react: useEffect callback never called

I want to report a bug. My problem is that the callback function I pass to useEffect() is never called in my app. I uploaded a minimal app here that reproduces the issue.

As you can see, I render a couple of nested components. After the initial render, I update the component state inside a useEffect() callback. However, this callback is only executed for the first two components, not for the third level component. The result is that the third and subsequent levels are not rendered at all.

I suspect that this is a bug in React and not a misuse on my side because doing any of the following changes will let the component tree render correctly in all levels:

  • Don’t use multiple React roots. If I remove the last (yellow) ReactDOM.render() call, then the second (red) component tree will render correctly.
  • Don’t conditionally render child components. Removing the message !== DEFAULT_MESSAGE check (main.tsx, line 20) causes the component trees to render correctly.
  • Use useLayoutEffect() instead of useEffect().

If you need additional information to reproduce the issue or have any questions, let me know. I’d like provide any help I can!

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 8
  • Comments: 16 (4 by maintainers)

Commits related to this issue

Most upvoted comments

Here is somewhat minimal reproduction:

function useTest() {
  const [effect, setEffect] = useState(false);
  useEffect(() => {
    setEffect(true);
  }, []);

  return effect;
}

function A() {
  const effect = useTest();

  return (
    <div >
      {"" + effect}
      {effect && <B/>}
    </div>
  );
}

function B() {
  const effect = useTest();

  return (
    <div >
      {"" + effect}
      {effect && <C/>}
    </div>
  );
}


function C() {
  const effect = useTest();

  return (
    <div >
      {"" + effect}
    </div>
  );
}

function Other() {
  useEffect(() => {}, []);
  return null;
}


ReactDOM.render(
  <A/>,
  document.querySelector("#root1")
);

ReactDOM.render(
  <Other/>,
  document.querySelector("#root2")
);

Some observations:

  • If you change the order of render()s, it works
  • If you use useLayoutEffect, it works
  • If Other doesn’t call useEffect, it works
  • If the second render() is delayed, it works
  • If B or C are rendered unconditionally, it works
  • If setEffect is delayed, it works

Fix released in 16.12.0

@WxSwen: I have no solution for the bug, but I found a workaround that works for me. Depending on your requirements, it might or might not help you, but I thought I might just share it. 😃

It seems the problem occurs when I have multiple React instances on the page (multiple ReactDOM.render() calls). And shortly after I found this bug and opened the issue, I also ran into another challenge with multiple React instances: I wanted to share context between all components on the page, but for that to work, they must be in the same React render tree.

So what I ended up with was to go back to a single React instance. Instead, I call ReactDOM.render() only once, render into a disconnected <div>, and use React portals to render my individual components into their respective places on the page. For example:

const appRoot = document.createElement("div"); // This div is never attached to the DOM
const comp1Root = document.querySelector("#root1");
const comp2Root = document.querySelector("#root2");
ReactDOM.render(
  <>
    {/* I can place shared context providers here, if I want */}
    {ReactPortal.create(<Comp1 />, comp1Root, "root1")}
    {ReactPortal.create(<Comp2 />, comp2Root, "root2")}
  </>,
  appRoot
);

@kunukn Sorry, I forgot to add an effect condition. I changed the useEffect() call (see diff), but the result stays the same: Components level 3 and beyond don’t render.

edit: Tested my change in your codesandbox, saw that I missed the level variable as an effect dependency, updated my repository again. Still the same error - as you can see from the missing console.log() output, the effect callback doesn’t even get called once.