#391 useEffect 2

medium
react

What will be the output in console for the following code:

function App() {
  const [show, setShow] = useState(true)
  return <div>
    {show && <Child unmount={() => setShow(false)} />}
  </div>;
}

function Child({ unmount }) {
  const isMounted = useIsMounted()
  useEffect(() => {
    console.log(isMounted)
    Promise.resolve(true).then(() => {
      console.log(isMounted)
    });
    unmount(); 
  }, []);

  return null;
};

function useIsMounted() {
  const isMounted = useRef(false);

  useEffect(() => {
    isMounted.current = true;
    return () => isMounted.current = false;
  }, []);

  return isMounted.current;
}

Source

On the initial render of App, the state show is true, so React renders <Child />.

Inside the render of Child, the useIsMounted hook creates a ref (isMounted = useRef(false)). During this render, isMounted.current is still false, and the hook returns that value. As a result, inside render, isMounted === false.

When React enters the commit phase, it runs Child’s effect. At this point, the effect from useIsMounted (which would normally set isMounted.current = true) has not yet run, so the ref is still false. The first console.log(isMounted) therefore logs false. A Promise.resolve().then(...) schedules a microtask to log again later, and then unmount() is called, which triggers setShow(false) in the parent and schedules Child to unmount.

Still in the same commit, after React finishes running effects for mounted components, it proceeds to unmount Child. During cleanup, the useIsMounted effect’s cleanup runs and sets isMounted.current = false. But since the ref was never flipped to true in the first place, it simply remains false.

Finally, when the microtask from the Promise runs after unmount, it logs the ref again. Because the component was removed before isMounted.current ever became true, the ref is still false.

✅ Final console output, in order:

  • false (first log in effect before unmount)
  • false (microtask after unmount)