Material-ui: [TextField] How to prevent mousewheel in input of type number?

Created on 30 Aug 2017  Â·  13Comments  Â·  Source: mui-org/material-ui

Problem description

Scrolling in a number field changes the values. In all the applications I have built so far people have asked me to prevent this behaviour because it sometimes causes corrupted data.

See also https://stackoverflow.com/questions/9712295/disable-scrolling-on-input-type-number/38589039#38589039

Steps to reproduce

Build a TextField of type number. Set focus into it. Scroll with your mousewheel.

Versions

This happens in all browsers in input elements, as far as I know.

Workaround

I have tried this:

document.addEventListener('mousewheel', function(event) {
  if (window.document.activeElement.type === 'number') {
    event.preventDefault()
  }
})

But it only seems to work sometimes. No Idea why it only works inconsistently.

Request

Now I see that changing standard behaviour would not be good. But adding this as an option would be very nice.

TextField question

Most helpful comment

Hi. It's working with :

<TextField type="number" onWheel={event => { event.preventDefault(); }} />

Hope it will helps.

All 13 comments

@barbalex I'm not sure to follow. What's preventing you from implementing this behavior? I can't think of anything we are doing blocking the behavior. You could try to reproduce the issue on a native <input /> to make sure it's Material-UI related and not React related.

I'm closing. Feel free to reopen if you can provide a reproduction example. codesandbox.io is a good place to start with.

Hi. It's working with :

<TextField type="number" onWheel={event => { event.preventDefault(); }} />

Hope it will helps.

Both my general workaround and @Tomlgls do not seem to work any more in Chrome. When you implement them an error appears:

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/6662647093133312

Linking to this page: https://www.chromestatus.com/features/6662647093133312

I have absolutely no idea why this would happen when you specifically add an onWheel event listener directly on the Input. But that is what happens.

Just to explain why this is important:

When users enter a value in a number field and then scroll the window, they inadvertently change the value they just have set. This is horrible UX and that is why I keep getting error reports about this "feature" that unfortunately is standard behavior. Also: The window does not scroll (but that is a lesser concern).

It obviously has nothing to do with material-ui but fact is that I have to somehow prevent this when using material-ui.

The issue is visible on the material-ui demo page. I have also prepared a demo that also shows that the onWheel does not prevent it from happening any more: https://codesandbox.io/s/material-demo-wdgpg?fontsize=14.

Instructions to reproduce the basic effect:

  1. visit https://codesandbox.io/s/material-demo-wdgpg?fontsize=14 using chrome
  2. click into first field. Enter a number
  3. without having moved your mouse scroll as if you were in a long form and wanted to scroll down further
  4. watch the number you just entered change

Instructions to reproduce that an onWheel event handler cannot prevent this:

  1. visit https://codesandbox.io/s/material-demo-wdgpg?fontsize=14 using chrome
  2. click into second field (which has the onWheel event handler implemented). Enter a number
  3. without having moved your mouse scroll as if you were in a long form and wanted to scroll down further
  4. watch the console

@barbalex I will confess I haven't looked at this problem in depth, but you can get around the passive event error by attaching it to a TextField using addEventListener e.g. https://codesandbox.io/s/n3scr

import React from "react";
import clsx from "clsx";
import { makeStyles } from "@material-ui/core/styles";
import MenuItem from "@material-ui/core/MenuItem";
import TextField from "@material-ui/core/TextField";

const useStyles = makeStyles(theme => ({
  container: {
    display: "flex",
    flexWrap: "wrap"
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
    width: 200
  },
  dense: {
    marginTop: 19
  },
  menu: {
    width: 200
  }
}));

const currencies = [
  {
    value: "USD",
    label: "$"
  },
  {
    value: "EUR",
    label: "€"
  },
  {
    value: "BTC",
    label: "฿"
  },
  {
    value: "JPY",
    label: "Â¥"
  }
];

function TextFields() {
  const classes = useStyles();
  const [values, setValues] = React.useState({
    name: "Cat in the Hat",
    age: "",
    multiline: "Controlled",
    currency: "EUR"
  });
  const textFieldRef = React.useRef(null);

  React.useEffect(() => {
    const handleWheel = e => e.preventDefault();
    textFieldRef.current.addEventListener("wheel", handleWheel);

    return () => {
      textFieldRef.current.removeEventListener("wheel", handleWheel);
    };
  }, []);

  const handleChange = name => event => {
    setValues({ ...values, [name]: event.target.value });
  };

  return (
    <form className={classes.container} noValidate autoComplete="off">
      <TextField
        id="standard-number"
        label="Regular Number field"
        value={values.age}
        onChange={handleChange("age")}
        type="number"
        className={classes.textField}
        InputLabelProps={{
          shrink: true
        }}
        margin="normal"
      />
      <TextField
        id="standard-number"
        label="Number Field with onWheel handler. Watch the console"
        value={values.age}
        onChange={handleChange("age")}
        type="number"
        className={classes.textField}
        InputLabelProps={{
          shrink: true
        }}
        margin="normal"
        ref={textFieldRef}
      />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
      <p>text to create a long form you will have to scroll</p>
      <br />
    </form>
  );
}

export default TextFields;

nice one @joshwooding, thanks!

An easy fix also working for new Chrome is:
<TextField type="number" onWheel={ event => event.currentTarget.blur() }} />

@peterbartos
Unfortunately your solution also produces this error: [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.

Only working solution so far seems to be @joshwooding https://github.com/mui-org/material-ui/issues/7960#issuecomment-497945204

@barbalex are you sure you tested exactly the blur() approach? I'm not calling there preventDefault(). Blur itself helps to avoid changing input number by scroll.

@peterbartos yeah, it sounds kind of weird that the error mentions preventDefault when onWheel only blured the current target.

Double checking I now see that [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. must have been caused by something else. I probably still had a preventDefault call in the component when testing blur(). Sorry for the negligence :-(

_BUT:_ onWheel={ event => event.currentTarget.blur() } does not prevent the numbers from changing when I scroll while focus is in a number field.

I tested this again by deactivating @joshwooding 's solution and adding yours.

What completely baffles me: This blurs:

  const onKeyDown = e => {
    if (e.key === 'Enter') {
      handleSubmit()
      // show user something happened
      e.currentTarget.blur()
    }
  }

This does not blur:

const onWheel = e => e.currentTarget.blur()

Just in case someone lands here looking for a working example, here is my TextField component. It is used with formik:

import React, { useCallback, useEffect, useRef } from 'react'
import Input from '@material-ui/core/Input'
import InputLabel from '@material-ui/core/InputLabel'
import FormControl from '@material-ui/core/FormControl'
import FormHelperText from '@material-ui/core/FormHelperText'
import styled from 'styled-components'
import { observer } from 'mobx-react-lite'

const StyledFormControl = styled(FormControl)`
  padding-bottom: 19px !important;
  > div:before {
    border-bottom-color: rgba(0, 0, 0, 0.1) !important;
  }
`

const MyTextField = ({
  field,
  form,
  label,
  type = 'text',
  multiLine = false,
  disabled = false,
  hintText = '',
  helperText = '',
  required = false,
}) => {
  const { onChange, onBlur, value, name } = field
  const { errors, handleSubmit } = form
  const error = errors[name]

  // only working solution to prevent whell scrolling from changing number values
  // see: https://github.com/mui-org/material-ui/issues/7960#issuecomment-497945204
  const textFieldRef = useRef(null)
  useEffect(() => {
    const handleWheel = e => e.preventDefault()
    textFieldRef.current.addEventListener('wheel', handleWheel)

    return () => {
      textFieldRef.current.removeEventListener('wheel', handleWheel)
    }
  }, [])

  // value should immediately update when pressing Enter
  const onKeyDown = useCallback(e => {
    if (e.key === 'Enter') {
      handleSubmit()
      // show user something happened
      e.currentTarget.blur()
    }
  })

  return (
    <StyledFormControl
      fullWidth
      disabled={disabled}
      error={!!error}
      aria-describedby={`${label}ErrorText`}
    >
      <InputLabel htmlFor={label} shrink required={required}>
        {label}
      </InputLabel>
      <Input
        id={name}
        ref={textFieldRef}
        name={name}
        value={value || ''}
        type={type}
        multiline={multiLine}
        onChange={onChange}
        onBlur={onBlur}
        onKeyDown={onKeyDown}
        placeholder={hintText}
        autoComplete="off"
        autoCorrect="off"
        autoCapitalize="off"
      />
      {!!error && (
        <FormHelperText id={`${label}ErrorText`}>{error}</FormHelperText>
      )}
      {!!helperText && (
        <FormHelperText id={`${label}HelperText`}>{helperText}</FormHelperText>
      )}
    </StyledFormControl>
  )
}

export default observer(MyTextField)

Hi. It's working with :

<TextField type="number" onWheel={event => { event.preventDefault(); }} />

Hope it will helps.

thanks .

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pola88 picture pola88  Â·  3Comments

anthony-dandrea picture anthony-dandrea  Â·  3Comments

revskill10 picture revskill10  Â·  3Comments

zabojad picture zabojad  Â·  3Comments

FranBran picture FranBran  Â·  3Comments