I use useState in my custom React hook useGame and I was pretty sure that updating state in the custom hook would trigger re-rendering of every component using the hook but it turned out it doesn't work this way
useGame hook
export const useGame = () => {
const [stage, setStage] = useState(STAGE.START);
const [minValue, setMinValue] = useState(DEFAULT_MIN);
const [maxValue, setMaxValue] = useState(DEFAULT_MAX);
const [number, setNumber] = useState(generateRandomNumberInRange(minValue, maxValue));
const generateNumber = () => {
const newNumber = generateRandomNumberInRange(minValue, maxValue);
setNumber(newNumber);
};
return { stage, setStage, number, generateNumber };
};
Game component
export const Game = () => {
const { stage } = useGame();
return (
<div style={{ display: "flex", flexDirection: "column", justifyContent: "center", alignItems: "center" }}>
{(() => {
switch (stage) {
case STAGE.START:
return <Start />;
case STAGE.PLAYING:
return <Playing />;
case STAGE.FINISH:
return <Finish />;
default:
return null;
}
})()}
</div>
);
};
Start component
export const Start = () => {
const { setStage } = useGame();
const handleClick = () => {
setStage(STAGE.PLAYING);
};
return (
<>
<button onClick={handleClick}>Start game</button>
</>
);
};
I was sure that on handleClick it would update state stage in useGame hook, Game component would re-render and since the stage had changed it would render Playing component this time. But it doesn't work this way. Could you please explain me what I am doing wrong and how to fix my code and make it work?
The useGame() calls in <Game /> and <Start /> aren't related — they will both have their own states. React doesn't exactly know that your useGame hook exists; it only sees that while rendering the <Game /> component (i.e. calling the Game function), useState gets called four times, and while rendering the <Start /> component (again, that is calling the Start function), useState gets called four times too. React can keep track of the eight state values by knowing where the <Game /> and <Start /> components exist in its tree model and combining that information with the order of the useState calls.
<Game />
useGame() (React doesn't see this call)
useState(STAGE.START) (React sees this) — State #0useState(DEFAULT_MIN) (React sees this) — State #1useState(DEFAULT_MAX) (React sees this) — State #2useState(generate…) (React sees this) — State #3<Start />
useGame() (React doesn't see this call)
useState(STAGE.START) (React sees this) — State #4useState(DEFAULT_MIN) (React sees this) — State #5useState(DEFAULT_MAX) (React sees this) — State #6useState(generate…) (React sees this) — State #7To solve this, you'll either need to move the state up (to a common ancestor component for <Game /> and <Start /> which will then pass the states and setters down as props or as a context value) or use an external state manager like Zustand, Redux, MobX etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With