React: useEffect callback never called

Created on 11 Oct 2019  ยท  14Comments  ยท  Source: facebook/react

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!

Hooks Bug

Most helpful comment

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

All 14 comments

I recreated this in https://codesandbox.io/s/react-typescript-4blmv

These is a lint warning that might help why this doesn't as intended work.

React Hook useEffect contains a call to 'setMessage'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [level] as a second argument to the useEffect Hook. (react-hooks/exhaustive-deps)

These lines should be changed as this leads to infinite look. In useEffect you are updating the state which triggers the useEffect, which updates the state, which triggers the useEffect .. repeatedly.

const [message, setMessage] = React.useState(DEFAULT_MESSAGE);
  React.useEffect(() => {
    const newMessage = `Level ${level}`;
    console.log(newMessage);
    setMessage(newMessage);
  });

@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.

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

i met this issue too.
if i use useLayoutEffect or fallback to class by use componentdidmount will be done.
if you solve this problem, tell me;

@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
);

@pschiffmann Even might not what i want but thanks for your kind and solution

I've hit this bug with React v16.9 and React Native, using two AppRegistry.registerComponent calls, which is the RN equivalent of two ReactDOM.render calls in the sense that they create a "React root".

Never mind. Failed to confirm this bug in React Native with the code provided by @vkurchatkin.

This looks bad.

Does someone want to turn this into a failing test case for ReactHooks-test.internal.js?

Does someone want to turn this into a failing test case for ReactHooks-test.internal.js?

https://github.com/facebook/react/pull/17335, but it's not failing as expected yet... ๐Ÿ˜…

Based on the code @vkurchatkin posted above.

edit: I've just tested on react-native-macos, and the demo in #17335 works fine, so I guess this bug doesn't affect me, which means I'm done helping on this.

Downgrading to React DOM 16.8 is a valid workaround.

Fix has landed in the Next channel: https://codesandbox.io/s/react-typescript-2thgm

I'll cut a release later today. Thanks for reporting!

Fix released in 16.12.0

https://github.com/react-navigation/hooks#only-for-react-navigation-v3--v4-not-v5

Was this page helpful?
0 / 5 - 0 ratings

Related issues

framerate picture framerate  ยท  3Comments

trusktr picture trusktr  ยท  3Comments

zpao picture zpao  ยท  3Comments

varghesep picture varghesep  ยท  3Comments

UnbearableBear picture UnbearableBear  ยท  3Comments