Home > Blockchain >  UI not re-rendering on state update using custom hook
UI not re-rendering on state update using custom hook

Time:11-07

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?

CodePudding user response:

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 #0
      • useState(DEFAULT_MIN) (React sees this) — State #1
      • useState(DEFAULT_MAX) (React sees this) — State #2
      • useState(generate…) (React sees this) — State #3
  • <Start />
    • useGame() (React doesn't see this call)
      • useState(STAGE.START) (React sees this) — State #4
      • useState(DEFAULT_MIN) (React sees this) — State #5
      • useState(DEFAULT_MAX) (React sees this) — State #6
      • useState(generate…) (React sees this) — State #7

To 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.

  • Related