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:
ReactDOM.render()
call, then the second (red) component tree will render correctly.message !== DEFAULT_MESSAGE
check (main.tsx
, line 20) causes the component trees to render correctly.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!
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:
render()
s, it worksuseLayoutEffect
, it worksOther
doesn't call useEffect
, it worksrender()
is delayed, it worksB
or C
are rendered unconditionally, it workssetEffect
is delayed, it worksi 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
Most helpful comment
Here is somewhat minimal reproduction:
Some observations:
render()
s, it worksuseLayoutEffect
, it worksOther
doesn't calluseEffect
, it worksrender()
is delayed, it worksB
orC
are rendered unconditionally, it workssetEffect
is delayed, it works