DEV Community

Cover image for React - Don't update parent state in the rendering phase of the child
Jacopo Marrone @tresorama
Jacopo Marrone @tresorama

Posted on

React - Don't update parent state in the rendering phase of the child

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>

Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>;
}

Enter fullscreen mode Exit fullscreen mode

Thank you for reading this blog post.
Not clear ? Question ? Ask in comments !!

Top comments (0)