skip to Main Content

I have a state that holds a function and this function use another state value but, when I change this second state value its value is not reflected in first state function.

I know that React is using an old version of my function but how can I solve this? Should I use useEffect to update the function if the second state changes? Should I use useCallback? I’m a little confused on this and when I should use one or another.

Here’s an example of my code:

type StepsActionType = {
  onStep: () => void;
  ... other props
}

...

const [stepsProps, setStepsProps] = React.useState<Partial<StepsActionType>>({});
const [currentStep, setCurrentStep] = React.useState<number>(0);

useEffect(() => {
  onReady()
}, [])

function onReady() {
  console.log("now ready");
  setStepsProps({
    onStep: () => console.log("current step", currentStep),
  });
}

function increaseStep() {
  setCurrentStep((current) => {
    const next = current + 1;
    console.log("next step", next);
    return next;
  });
}

function handleStep() {
  stepsProps.onStep?.();
}

When my app is mounted it sets a step handler function to stepsProps state but if the step is increased calling increaseStep the step handler keeps saying I’m on step 0 instead of 1.

Here’s a codesandbox I’ve made.

2

Answers


  1. You could use an additional ref to keep track of the latest value of the step.

    const stepRef = useRef(currentStep);
    useEffect(() => stepRef.current = currentStep, [currentStep]);
    // ...
    function onReady() {
      console.log("now ready");
      setStepsProps({
        onStep: () => console.log("current step", stepRef.current),
      });
    }
    
    Login or Signup to reply.
  2. React documents suggest to avoid using useEffect to adjust some state when a prop changes

    Instead you could calculate stepsProps during rendering. Then, you wont’t need useEffect at all.

    //...
    const [currentStep, setCurrentStep] = React.useState(0);
    
    const stepsProps = {
      onStep: () => console.log("current step", currentStep),
    };
    
    function increaseStep() {
      setCurrentStep((current) => {
        const next = current + 1;
        console.log("next step", next);
        return next;
      });
    }
    
    function handleStep() {
      stepsProps.onStep();
    }
    //...

    if stepsProps is costly and you want to avoid calculating it on every render you could use memoization:

      const stepsProps = React.useMemo(
        () => ({
          onStep: () => console.log("current step", currentStep),
        }),
        [currentStep, ..otherDependenciesIfNeeded]
      );
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search