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
Build a TextField of type number. Set focus into it. Scroll with your mousewheel.
This happens in all browsers in input elements, as far as I know.
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.
Now I see that changing standard behaviour would not be good. But adding this as an option would be very nice.
@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:
Instructions to reproduce that an onWheel event handler cannot prevent this:
@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)
A great alternative solution https://github.com/mui-org/material-ui/issues/10582#issuecomment-603118446
Hi. It's working with :
<TextField type="number" onWheel={event => { event.preventDefault(); }} />
Hope it will helps.
thanks .
Most helpful comment
Hi. It's working with :
Hope it will helps.