skip to Main Content

I created a hook, whose purpose is adding cherries to an array:

import { useReducer, useRef } from "react";

const useCherry = () => {
    const myRef = useRef(0);

    const [state, dispatch] = useReducer(
        (state, action) => {
            if (action.type === "add") {
                myRef.current += 1;
                return [...state, "🍒"];
            }

            return state;
        },
        []
    );

    return [state, dispatch, myRef];
};

export default useCherry;

Then I call it on button click:

<button
    type="button"
    onClick={() => {
        console.log(myRef.current); // Logs 0
        dispatch({ type: "add" });
        console.log(myRef.current);// Logs 0, expected 1
    }}
>
    Add more cherry
</button>

I don’t understand why would cherryRef.current return an old value after calling dispatchCherry(). In theory it should return the incremented one, as myRef is an object.

I’m trying to get the incremented value right after calling the dispatch().

2

Answers


  1. I don’t understand why would cherryRef.current return an old value
    after calling dispatchCherry(). In theory it should return the
    incremented one, as myRef is an object.

    Yes, it does return the incremented value, but since it does not complete the trigger of a re-render yet, it is not shown to you (because useRef is not exactly a state variable). The useState/useReducer hook can be used to manually trigger, or you can change myRef to a state variable.

    Example, this can give you next updated value:

    dispatch({ type: "add" });
    setTimeout(() => {
      console.log(cherryRef.current); // Logs 1
    }, 0);
    
    Login or Signup to reply.
  2. Because dispatch() runs and updates the state for the next render, the second console.log() runs before it, and thus returns the old value.

    To store and retrieve the useRef value before and after the change separately (outside of the state), consider to export a customized dispatch() for use in another component.

    const myDispatchCherry = (action) => {
      if (action?.type === "add") myRef.current += 1;
      dispatch(action);
    }
    

    This could also keep the original dispatch() pure to its function, and add the additional tasks as an optional and customizable part.

    Test the follow code here: stackblitz

    import React, { useReducer, useRef } from "react";
    
    const useCherry = () => {
        const myRef = useRef(0);
        const [state, dispatch] = useReducer(
            (state, action) => {
                if (action.type === "add") {
                    return [...state, "🍒"];
                }
                return state;
            },
            []
        );
    
    const myDispatchCherry = (action) => {
      if (action?.type === "add") myRef.current += 1;
      dispatch(action);
    }
        return {state, dispatch, myRef, myDispatchCherry};
    };
    
    export default useCherry;
    
    export default function App() {
      const {state, myRef, myDispatchCherry} = useCherry();
      return (
        <div>
          <p>{`Cherry: ${state}`}</p>
          <button
        type="button"
        onClick={() => {
            console.log(`myRef count before adding: ${myRef.current}`); // Logs 0
            myDispatchCherry({ type: "add" });
            console.log(`myRef count after adding: ${myRef.current}`); // Logs 1
        }}
    >
        Add more cherry
    </button>
        </div>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search