I have prepared a simple test project at Github to demonstrate my problem:
There is a MUI TextField, a thumbs up or down icon and a text string with word description.
The custom App.jsx is shown below:
const DICT = {
AA: "Rough, cindery lava",
AB: "An abdominal muscle",
ABS: "Abdominal muscles",
ABSQUATULATE: "",
ABSQUATULATING: "",
ABY: "Pay the penalty",
};
export default function App() {
const [word, setWord] = useState("");
const [description, setDescription] = useState("");
const [found, setFound] = useState(false);
const handleChange = (ev) => {
ev.preventDefault();
const key = ev.target.value.trim().toUpperCase();
setWord(key);
if (key.length < 2) {
setDescription("");
return;
}
setFound(DICT.hasOwnProperty(key));
if (!found) {
setDescription("The word is not found in the dictionary");
return;
}
setDescription(DICT[key] || "");
};
return (
<Box component="form" noValidate autoComplete="on">
<TextField
id="wordInput"
label="Enter a word"
value={word}
onChange={handleChange}
/>
<Box>
{found ? <ThumbUp color="primary" /> : <ThumbDown color="error" />}
</Box>
<Box>{description}</Box>
</Box>
);
}
As you can see in the animated screenshot above, when a user keeps typing or deleting "A" into the TextField, then the word description found in the DICT
object is displayed inconsistently and it takes few tries until "Rough, cindery lava" is displayed for "AA".
My problem is probably wide spread among ReactJs newbies like myself. And I think I should use the prevState
form for a hook, but which one of the 3 and how? I am confused.
3
Answers
When you run
setKey()
the value isn’t updated until the next render, so you can’t access that value (consistently) within the function.useEffect is designed to respond to changes in state:
and another to respond to those changes:
The problem is this code:
remember: the useState hook is not synchronous. So, you cannot validate the value just after setting it.
you need to use the useEffect hook in order to listen to the state change:
but I think it is not necessary to use the found State, just the description state. Like this:
The state inside
handleChange
is the current state when called. All state updates would affect the next invocation ofhandleChange
. This means that when you do this:The
found
state you’re using is one step behind. The value would only update after the render caused by setting the state.The rookie mistake you’re making is having a state for computed values. Computed values are derived out of the current state, and don’t need a state of their own. You can compute the value of
found
anddescription
on each render, and store them in aconst
instead. This would reduce the need for redundantuseEffect
calls that update other states, that cause multiple unnecessary re-renders, and make it harder to trace the flow.If the computation is resource heavy, and the component might render because of unrelated changes, you can use
useMemo
or memoize the component to avoid unnecessary calculations. In your case, the cost of computing bothfound
anddescription
is negligible.Example (see comments in code):