skip to Main Content
import { useRef, useState } from "react";
import "./styles.css";

function usePrevious(value) {
  const currRef = useRef();
  const previousValue = currRef.current;
  currRef.current = value;
  return previousValue;
}

export default function App() {
  const [counter, setCounter] = useState(0);
  const previousValue = usePrevious(counter);

  return (
    <div className="App">
      <h1>{counter}</h1>
      <h1>{previousValue}</h1>
      <h2 onClick={() => setCounter((prev) => prev + 1)}>btn</h2>
    </div>
  );
}

I was trying an implementation for usePrevious, but both the previousValue and the counter are the same values when the button is clicked for the first time, why?

I tried tracing it but I couldn’t reach to any explanation.

2

Answers


  1. The reason you’re seeing the same value is that your app is wrapped in StrictMode, which causes components to render twice in development mode. This behavior is intentional and helps identify potential issues in your code. The function you’re referring to is deterministic, meaning that it produces the same result when called with the same arguments. Therefore, the value remains consistent across the two render cycles.

    In React, this double rendering in StrictMode is designed to ensure that components and functions handle state and side effects correctly and are resilient to unexpected behaviors. By observing how your components behave under these conditions, you can ensure that they work reliably and predictably.

    const usePrevious = (value) => {
      const currRef = useRef(); //To have a synchronized value before making a change, you need to pass the current value.
    
      //useLayoutEffect for see the change before ui rendered 
      useLayoutEffect(() => {
        currRef.current = value;
      }, [value]);
    
      return currRef.current;
    };
    
    Login or Signup to reply.
  2. It appears your usePrevious hook implementation isn’t updating the internal React ref as part of the React component lifecycle and the React.StrictMode component is surfacing the issue.

    The typical usePrevious hook recipe uses the useEffect hook to update the ref though.

    Example:

    import { useEffect, useRef } from "react";
    
    function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      }, [value]);
      return ref.current;
    }
    
    function usePrevious(value) {
      const ref = React.useRef();
      React.useEffect(() => {
        ref.current = value;
      }, [value]);
      return ref.current;
    }
    
    function App() {
      const [counter, setCounter] = React.useState(0);
      const previousValue = usePrevious(counter);
    
      return (
        <div className="App">
          <h1>Counter: {counter}</h1>
          <h1>Previous: {previousValue}</h1>
          <button type="button" onClick={() => setCounter((prev) => prev + 1)}>
            btn
          </button>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    const root = ReactDOM.createRoot(rootElement);
    
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    <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" />

    Oddly enough I’m able to reproduce the issue with your code in a running Codesandbox with your exact code copy/pasted in, but here in a running stack snippet your code runs exactly as expected.

    function usePrevious(value) {
      const currRef = React.useRef();
      const previousValue = currRef.current;
      currRef.current = value;
      return previousValue;
    }
    
    function App() {
      const [counter, setCounter] = React.useState(0);
      const previousValue = usePrevious(counter);
    
      return (
        <div className="App">
          <h1>Counter: {counter}</h1>
          <h1>Previous: {previousValue}</h1>
          <button type="button" onClick={() => setCounter((prev) => prev + 1)}>
            btn
          </button>
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    const root = ReactDOM.createRoot(rootElement);
    
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    <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" />

    I’d still suggest/recommend the version that uses the intentional side-effect to update the ref, i.e. the version using useEffect.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search