My goal is to perform an action when keyboard button is pressed, using current value of variable that I declared with useState
.
I reproduced my problem in code example below:
import React from 'react';
import { HotKeys } from 'react-hotkeys';
import { useCallback, useEffect, useMemo, useState } from 'react';
const keyMap = {
MOVE_DOWN: 'a'
};
export function App(props) {
const [test, setTest] = useState(0);
const focusNextRow = useCallback(() => {
console.log("test", test);
setTest(test => test + 1);
console.log("test2", test);
}, [test]);
useEffect(() => {
console.log("TEST IS NOW ", test);
}, [test]);
const handlers = {
MOVE_DOWN: focusNextRow
};
return (
<div className='App'>
<HotKeys keyMap={keyMap} handlers={handlers}>
<div>test</div>
</HotKeys>
</div>
);
}
Result looks like this:
As you can see: test
value in component is updated, but method that is called on keyboard press has always default value of this state (which is 0). How can I fix that?
3
Answers
Your unexpected behaviour seems to be coming from the usage of
useCallback
Apparently, it is called Clusure Issue referenced in this question.
TL;DR
It appears that the
HotKeys
component does a bit of configuration memoization where it doesn’t "listen" to updates to certain props.You can use the
allowChanges
prop to let theHotKeys
component react to thehandlers
changes/updates. See Component Props API for details.That said, this still leaves a basic stale Javascript closure problem where you are attempting to log the React state after enqueueing a state update. React state updates are processed asynchronously, so you can’t ever log the state immediately after it’s enqueued anyway. See The useState set method is not reflecting a change immediately for full details.
The
useEffect
hook is the correct method for logging state updates.If you wanted, you could log the "before" and "after" values in the state updater function
but since the state updater functions are to be considered pure functions this should generally be avoided. But it could be useful/handy as a debugging step.
In
useCallback
, do not addtest
as a dependency of the callback.no:
yes:
I also suggest using a name like
prevTest
for the parameter of the lambda function you pass tosetTest
so that you are reminded that it is not the current value oftest
and does not use the same name as theuseState
hook. (test
, andsetTest
are already defined in the enclosing scope. It may be confusing to usetest
again here as the argument name.)no:
yes:
And finally, be careful about what you are logging. It doesn’t make sense to log the value of
test
after callingsetTest
because of the way react state hooks work. See the documentation of useState. At this point I suggest just removing your log messages.Final version: