skip to Main Content

React Strict Mode forces useEffects to double render. The problem is that this causes the cleanup code to run on page enter AND exit. Instead of just on exit like the cleanup should do.

useEffect(() => {
    return () => {
        console.log('do you only call once?')
        // TODO: Fix why this is resetting to init state when I ENTER instead of just leave
        // dispatch(resetToInitialState())
    }
}, [])

The above code will trigger once when entering the page, another time when exiting. The issue is that I’d set states before navigating in the page, and the React Strict mode would mean the cleanup resets everything to initial state…

Is there anyway to prevent cleanup from being called on page enter without disabling React Strict mode? Note that I fixed it by disabling React Strict mode, but wanted to keep it on if possible.

3

Answers


  1. I don’t think this can be fixed. This is the native useEffect behavior with Strict Mode, described in the documentation.

    https://react.dev/reference/react/StrictMode

    Your components will re-run Effects an extra time to find bugs caused by missing Effect cleanup.
    
    Login or Signup to reply.
  2. In React Strict Mode, useEffect cleanup functions are intentionally invoked twice during development for certain scenarios. This helps developers identify potential side effects or cleanup-related issues. Specifically, React calls the useEffect cleanup:

    1. On unmount.
    2. On component mount when the component is re-mounted immediately,
      simulating unmounting and re-mounting to highlight side effects.

    If you want to ensure your cleanup logic runs only on an actual unmount (and not during the development-only re-render simulation caused by Strict Mode), you can handle this by detecting whether the component is being unmounted permanently. Here’s how you can achieve it:

    Using a ref to track mounting state

    import { useEffect, useRef } from "react";
    
    function MyComponent() {
      const isMounted = useRef(false);
    
      useEffect(() => {
        console.log("Effect logic executed");
    
        return () => {
          if (isMounted.current) {
            console.log("Cleanup logic executed on unmount");
          }
          isMounted.current = true;
        };
      }, []);
    
      return <div>My Component</div>;
    }

    Explanation

    1. The isMounted ref initializes as false and remains consistent across
      renders.
    2. When the component is mounted for the first time, the cleanup logic
      won’t execute because isMounted.current is false.
    3. During subsequent mount/unmount cycles (including those simulated by
      Strict Mode), the cleanup function is only executed when the
      component is truly unmounted.

    Key Considerations

    Strict Mode Behaviour: This behaviour occurs only in development mode. In production, the useEffect cleanup runs once on unmount, as expected.

    Purpose: React’s strict mode aims to make your app more robust by simulating certain conditions during development. Avoid suppressing this behaviour unless absolutely necessary.

    By handling it this way, your app retains proper behaviour while ensuring a clear separation between development simulations and production unmounting.

    Login or Signup to reply.
  3. Be sure that the clean up code of useEffect hook will fire only on unmount event of a component. It will never run on mounting a component.

    Please check the logs below create by the following sample code.

    Please note that there "Unmounted" has always been logged after "Mounted". There is no "Unmounted" logged repeatedly.

    If your observation was right, then there should have been repeated "Unmounted" logs created. It does not happen.

    App.js

    import { useEffect, useState } from 'react';
    
    export default function App() {
      const [someState, setSomeState] = useState(false);
      return (
        <>
          {!someState && <TestComponent />}
          {someState && <TestComponent />}
          <br />
          <button onClick={() => setSomeState(!someState)}>
            Toggle Mount/Unmount
          </button>
        </>
      );
    }
    
    function TestComponent() {
    
      useEffect(() => {
        console.log('Mounted');
        return () => {
          console.log('Unmounted');
        };
      }, []);
    }
    

    Console logs

    The below three logs created on the initial render

    // Mounted
    // Unmounted
    // Mounted
    

    The below four logs created on clicking the button once

    // Unmounted
    // Mounted
    // Unmounted
    // Mounted
    

    The below four logs created on clicking the button a second time

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