Hi, I use Input controlled mode and subscribe to onKeyDown
and onChange
event. It will trigger an effect when user enter space. However, the space don't display on screen. Is this a bug on React or is there something wrong with the way I'm using it. But subscribe to onKeyUp
is normal. This is confused.
React version: 16.13.1 and old
Link to code example:
import React, { useState,useEffect } from 'react'
const App = () => {
const [count, setCount] = useState(0)
const [value, setValue] = useState('')
const [code, setCode] = useState('add')
useEffect(() => {
if (code === 'add') {
setCount(c => c + 1)
} else {
setCount(c => c - 1)
}
}, [code, setCount])
const keyDown = e => {
if (e.keyCode === 32) {
console.log('space~')
if (code === 'add') {
setCode('minus')
} else {
setCode('add')
}
}
}
const handleChange = e => {
setValue(e.target.value)
}
return (
<div>
<div>{count}</div>
<br />
<input onKeyDown={keyDown} value={value} onChange={handleChange} />
</div>
)
}
export default App
input element can't receive the space
input element can receive the space
It looks like the state update in the onKeyDown
handler interrupts the event and prevents the onChange
handler from being called. Because controlled components show only what you tell them to (the value
state in your example) this results in the space character never appearing.
This looks like a bug to me in the way these two events interplay.
cc @trueadm @gaearon for their event system expertise
Hi, I'm new here, trying to learn React architecture.
@bvaughn Here is something that I found about the problem.
onKeyDown
event doesn't block onChange
rather that it triggers earlier than input is modified. Basically, onKeyDown
goes earlier than onChange
because text input happens in onKeyUp
.
In this particular case, the problem is that within keyDown
, when setCode
calls, on the first dispatchAction
_Fiber_ has memoizedState
without the new character (obviously) and e
which is going to be added. In the end, it adds the new character to the input. Then useEffect()
triggers dispatchAction
again within the _same queue_ (not sure if it's queue) but it doesn't know anything about updated props. As a result, when it commitRoot
, it restores pendingProps
which are the same as they were in memoizedState
. In short, the character (space in this example) is added and then immediately old state restores without the new character (space), so it looks like it doesn't add anything.
As an example of not using _same queue_ (?), this particular example:
useEffect(() => {
if (code === 'add') {
setCount(c => c + 1)
} else {
setCount(c => c - 1)
}
}, [code, setCount])
could be modified to:
useEffect(() => {
if (code === 'add') {
setTimeout(() => setCount(c => c + 1), 0)
} else {
setTimeout(() => setCount(c => c - 1), 0)
}
}, [code, setCount])
and it will work.
I hope it makes sense.
Ran into this same issue, wrapping my setState call in a a setTimeout in the keydown handler fixed it.
I'm curious to know if this is something that should work differently under the hood in React or if this error points to a bigger mistake or bad pattern on the developer side?