TLDR;
Don't update parent state in the rendering phase of the child component
Long story
Visualize in your mind this Abstract React tree:
<WebSite>
<LoadingSpinner />
<PageContent>
<A>
<ASub>
<ASubSub>
<LoadingTriggerA />
</ASubSub>
</ASub>
</A>
<B>
<BSub>
<BSubSub>
<LoadingTriggerB />
</BSubSub>
</BSub>
</B>
</PageContent>
</WebSite>
The goal is to render a single LoadingSpinner in our whole website, and being able to trigger the LoadingSpinner visibility from LoadingTriggerA
and LoadingTriggerB
when they need to.
How to solve this without passing functions down the tree - aka “prop drilling“ ?
React Context, using a "useAppLoading" custom hook.
This custom hook takes care of maintaining the visibility state of the Loading component, and to render it.
This hook exposes us a show
and a hide
function.
In this post we are focused not on the custom hook, but here you can find the code for build a "useAppLoading" custom hook.
import { AppLoadingProvider, useAppLoading } from './my-custom-hook/useAppLoading';
const Website = () => {
return (
<AppLoadingProvider>
<PageContent /> // LoadingTriggerA and LoadingTriggerB are descendants of this
</AppLoadingProvider>
);
};
const LoadingTriggerA = () => {
const {showLoading, hideLoading} = useAppLoading();
...
return <div>....</div>;
}
const LoadingTriggerB = () => {
const {showLoading, hideLoading} = useAppLoading();
...
return <div>....</div>;
}
It seams ok.
But how do we call "show()" and "hide()" functions ???
This is THE POINT of this post.
Maybe like this ??
const LoadingTriggerA = () => {
const {showLoading, hideLoading} = useAppLoading();
showLoading();
return <div>....</div>;
}
const LoadingTriggerB = () => {
const {showLoading, hideLoading} = useAppLoading();
hideLoading();
return <div>....</div>;
}
Try yourself and you will notice that React javascript console triggers an error in the console saying:
Warning:
Cannot update a component (`AppLoadingProvider`) while rendering a different component (`LoadingTriggerA`).
To locate the bad setState() call inside `LoadingTriggerA`,
follow the stack trace as described in https://reactjs.org/link/setstate-in-render
What this means is that, child component can't update one of his parents component's state from within the rendering body.
That's what this warning is telling you.
This is an Anti Pattern because if it was legal, there will be chances that data flow going crazy, and weird stuff will happen, like unnecessary re rendering of the tree.
In our case the Parent is the AppLoadingProvider, which treat LoadingTriggerA and LoadingTriggerB as descendants.
So How to solve that ??
Update the (parent) state inside an useEffect, because useEffect runs after the main rendering phase of a component.
const LoadingTriggerA = () => {
const [busy, setBusy] = React.useState(false);
const {showLoading, hideLoading} = useAppLoading();
React.useEffect(() => {
if (busy) showLoading();
else hideLoading();
}, [busy]);
return <div>....</div>;
}
Thank you for reading this blog post.
Not clear ? Question ? Ask in comments !!
Top comments (0)