Search code examples
react-hooksreact-statereact-state-management

storing previous states of a hook


I want to print the current and previous value of a state, I have a piece of code that does the job

function App() {
  const [value, setValue] =  useState("");
  const prevValue = useRef('')
  useEffect(() => {
    prevValue.current = value;
  }, [value]);
  return (
    <div>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <div>
        Curr Value: {value}
      </div>
      <div>
        Prev Value: {prevValue.current}
      </div>
    </div>
  );
}

But the problem with it is if I have some other state that is being set through, say, a button, it will cause the prev value to be same as current value. I'm looking for a way to prevent this, can someone please help?

Thanks.


Solution

  • Updating the ref with useEffect works for a single render, because the ref is updated after the content has been rendered.

    As you can see in here, the useEffect is triggered when value changes. This means that at the end of each render cycle, prevValue.current equals value.

      useEffect(() => {
        prevValue.current = value;
      }, [value]);
    

    If another render is triggered by an unrelated update, the ref is already equal to the current value, and both are re-rendered.

    To always maintain the ref one step behind the value, use an updater function when setting the state. The updater function is called with the previous state, that you can store in the ref. Return the new state from the function to update the state.

    The the example, you can see that the the ref always equals to the previous value, even if another update (clicking the button) causes a re-render:

    const { useState, useRef } = React;
    
    function App() {
      const [anotherValue, setAnotherValue] = useState(0);
      const [value, setValue] =  useState('');
      const prevValue = useRef('');
      
      const onChange = e => {
        const newVal = e.target.value;
        
        // use an updater function
        setValue(prev => {
          prevValue.current = value; // set the previous value the the ref
          return newVal; // return the updated value to the state
        });
      };
    
      return (
        <div>
          <input
            value={value}
            onChange={onChange}
          />
          <div>
            Curr Value: {value}
          </div>
          <div>
            Prev Value: {prevValue.current}
          </div>
          <button onClick={() => setAnotherValue(v => v +1)}>
            Another value: {anotherValue}
          </button>
        </div>
      );
    }
    
    ReactDOM
      .createRoot(root)
      .render(<App />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>