React: Bug: too hard to fix "Cannot update a component from inside the function body of a different component."

Created on 28 Feb 2020  Â·  109Comments  Â·  Source: facebook/react

# Note: React 16.13.1 fixed some cases where this was overfiring. If upgrading React and ReactDOM to 16.13.1 doesn't fix the warning, read this: https://github.com/facebook/react/issues/18178#issuecomment-595846312

React version:

16.13.0

Steps To Reproduce

  1. Build a time machine.
  2. Go to the year 2017.
  3. Build a huge application of 10K lines of code.
  4. Get 80 (!) dependencies at package.json file including ones that become no longer maintained.
  5. Update React to the latest version at February 27, 2020.
  6. Get tons of errors that you don't know how to fix.
  7. Tell your client that fixes are going to take unknown time and it's going to cost $$$ + days or weeks of investigation or we're going to get stuck with the outdated version of React and related libraries forever which will cost more $$$ but later.

Being serious, the business I work for isn't interested on that at all. Obviously I'd never made it happen to get such warnings to appear if I'd get them earlier. Currently that's impossibly hard to make the errors to be fixed because I get them at many different cases and with a huge stack trace. I tried to fix at least one of the appearing errors and it already took a lot of time. I tried to debug some of used libraries but got no luck.

Just one example:

image

There we can notice the use of an outdated react-router, an outdated redux-connect (which I had to put to the project source to fix errors of outdated componentWillReceiveProps method), some HOCs created by recompose etc. It isn't just a simple virtual DOM tree where I can walk thru components developed by me and search by setState string to fix the bug, that's way more complicated than that.

Please make an "UNSAFE" option to disable this error or provide a simpler way to find where the error is thrown 🙏

Discussion

Most helpful comment

To the future commenters. I understand seeing a new warning is frustrating. But it points out legitimate issues that are likely causing bugs in your code. We would very much appreciate if you could refrain from expressing sarcasm and annoyance.

If you can't understand where it's coming from in the stack traces, please post screenshots or create reproducing sandboxes and we'll try to help. Most of these are probably coming from a few libraries, so the most productive thing to do is to reduce these cases and then file issues with those libraries.

Thank you all.

All 109 comments

I wish we had added this warning earlier. I'm sorry we didn't. It was an oversight with the introduction of Hooks. I believe this must be caused by newer code that uses Hooks, because there was already the same warning for classes much earlier so any earlier code would've already seen this warning.

Note that this will likely start hard failing in future versions of React. Intentional or otherwise (we've had a lot of bugs with this pattern). So regardless, you might end up getting stuck on an old version. If it's not possible to fix it, I'd recommend pinning to an older version of React.

However, that said, we want to help you and make it as easy as possible to fix these, including seeing if we can get help from library authors to publish fixed versions.

If you expand the little > arrow in the Chrome console you should also see an additional Stack trace (in addition to the Component stack in your screen shot). Can you post that too? That should show you the exact callsite that is causing a side-effect in render.

With me this appears when i use formik

image

@sebmarkbage thank you for the response. The stacktrace appearing after clicking > is ridiculous. It contains 200+ items!

I was going to paste it there or give a link to pastebin but tried a different direction. I walked thru Github issues of some of used libraries and found out that one of suspects is redux-form: https://github.com/redux-form/redux-form/issues/4619. I hope that's the only library which causes the errors and I'm going to wait for a fix before upgrading React.

But still, I'd ask to not close the this issue and I propose other developers to mention here libraries which also cause these errors.

@RodolfoSilva are you sure that it is caused by formik? If yes, can you please create an issue there and post a link to it here? I'm going to create a list of such issues at the beginning of my first message if the list is going to contain more than one item.

This really needs to be addressed ASAP. It makes upgrading impossible. The error trace is pretty much impossible.

Hm. I wonder if describing which line to look for in the error message would help.

In this case the first line to look for is the line after dispatchAction. That should be the thing that calls into React.

@RodolfoSilva can you post the source of FormItemInput.js, if it's something you can share? That seems to be calling dispatch or setState on line 71.

I think it is imperative that this error message be modified to include exactly which line of code is causing the error. It's almost impossible to pinpoint whether it is local code or library code that's causing the issue. External librairies libraries like react-redux and react-router are most likely the culprit may very well be the culprits yet it is impossible to easily determine that.

I'm pretty sure that React-Redux is not at fault here, but agreed that the error message and component stack make it kind of hard to know what's actually going on.

I face the same problem with Redux-form!

I have the same problem and I'm seeing that the warning is showing the first time that i write in my redux field or when i clear all it

I have that problem too, this is my component:

`const [price, setPrice] = useState(0);

const updatePrice = (newPrice) => {
setPrice(newPrice)
}
< CardContainer onPriceUpdated={updatePrice} > `

So, in this case my CardContainer component, notify to the parent component when the price is updated and the parent component renders the new prince.
So i guess React is warning me that i'm trying to update the state of a component using the function of other component.
I'm new in react, so i'm not sure if this is a React pattern or is in fact a bug.

If you guys have any suggestion to solve this warning i would appreciate it``

@l0gicgate

I think it is imperative that this error message be modified to include exactly which line of code is causing the error.

There are limits to what we can do in JavaScript. But all the information is in the stack you see in the browser. All you need to do is to skip the lines that are inside React.

To see the JavaScript stack, you need to click on a small arrow next to the error message.

For example, look at this screenshot from earlier:

75614021-cb812980-5b12-11ea-8a6e-a38f4cd6aeef

I appreciate it's a bit annoying to dig through the stack, but it shouldn't be that hard to skip the first several frames. The very next frame is the source of the problem. In this case, it seems like something inside the Formik library. So you can file an issue with it.

@martinezwilmer This example is too small to help. Create a sandbox please.

To the future commenters. I understand seeing a new warning is frustrating. But it points out legitimate issues that are likely causing bugs in your code. We would very much appreciate if you could refrain from expressing sarcasm and annoyance.

If you can't understand where it's coming from in the stack traces, please post screenshots or create reproducing sandboxes and we'll try to help. Most of these are probably coming from a few libraries, so the most productive thing to do is to reduce these cases and then file issues with those libraries.

Thank you all.

Hard to find the precise line concerned by the warning here:

@gaearon do you again have one tip about it ?

image

Hard to find the precise line concerned by the warning here:

What makes it hard? As I noted above it’s the first line that doesn’t say “react” on the right side. In this case, it’s connectAdvanced from Redux. Please file an issue in React Redux so that the maintainers have a chance to look at this.

As I said upthread, I would be _very_ surprised if whatever's happening here is an issue with React-Redux.

That said, I'm also not even sure exactly what triggers this message in the first place. I half-get what the error message is saying, but what kind of app code pattern would actually be an example of that behavior?

I ran into this recently and the fix was wrapping setState handler call sites in useEffect, like so: https://github.com/airbnb/lunar/commit/db08613d46ea21089ead3e7b5cfff995f15c69a7#diff-1c3bdd397b1ce5064142488877045306R56 (onChange and onSubmit use setState higher up the chain).

@martinezwilmer Where is onPriceUpdated being called? Maybe try wrapping it in useEffect?

The same issue seems to be happening for urql

We are using use-subscription + wonka (for streams) to orchestrate our updates, however an update can come in synchronously. Here we have already fetched the todos so if we click the Open button the result should just instantly pop up, however this seems to trigger the following error.

image

In our case this is intended, we can't just show fetching: true for a sync result, that would result in jumpy interfaces.

This is starting to crop up more and more in third party libraries now: urql, Apollo.

I ran into this and for several hours I assumed the problem was in my code. The condensed stacktrace points at my components, and it's not unusual for me to see third party libraries in the expanded stacktrace when I _did_ explicitly trigger an error. From my (albeit limited) research into this particular warning, it seems that most developers are not causing this issue themselves, but rather depending on code that does. Usually it's good practice to assume it isn't a bug upstream but when it is an upstream bug, wasting time tracking down a problem in your code that doesn't exist is rather frustrating. Is there anything React can do to help an average user determine if it was code they wrote, or code they depend upon that caused the issue?

One thing I note from the Apollo issue is:

The warning's stacktrace is showing the component that initialed the changes, not the one that gets re-renderd [sic] by those changes

If this is correct, can React give more information here? Perhaps telling us both the initiating component and the components that it caused to be updated?

Like @hugo , I encountered this when testing a new Ionic application:

  1. npx ionic start demo sidemenu --type=react
  2. react-scripts test

Indeed, the cause is buried in the middle and bottom of the stack trace.

console.error node_modules/react-dom/cjs/react-dom.development.js:88
    Warning: Cannot update a component from inside the function body of a different component.
        in Route (at App.tsx:37)
        in View (created by StackManagerInner)
        in ViewTransitionManager (created by StackManagerInner)
        in ion-router-outlet (created by IonRouterOutlet)
        in IonRouterOutlet (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (created by StackManagerInner)
        in StackManagerInner (created by Context.Consumer)
        in Unknown (created by Component)
        in Component (created by ForwardRef(IonRouterOutlet))
        in ForwardRef(IonRouterOutlet) (at App.tsx:36)
        in ion-split-pane (created by IonSplitPane)
        in IonSplitPane (created by ForwardRef(IonSplitPane))
        in ForwardRef(IonSplitPane) (at App.tsx:34)
        in NavManager (created by RouteManager)
        in RouteManager (created by Context.Consumer)
        in RouteManager (created by IonReactRouter)
        in Router (created by BrowserRouter)
        in BrowserRouter (created by IonReactRouter)
        in IonReactRouter (at App.tsx:33)
        in ion-app (created by IonApp)
        in IonApp (created by ForwardRef(IonApp))
        in ForwardRef(IonApp) (at App.tsx:32)
        in App (at App.test.tsx:6)

This issue was the closest I could find regarding this problem.

We found a specific pattern that causes this issue with mobx over in https://github.com/mobxjs/mobx-react/issues/846

@sebmarkbage I can no longer reproduce this problem. We have update some libraries and the problems over.

@jgoux we seem to face the same problem @Clovis ^^ Spotted

I started getting this error after the updating react to react 16.13.0. The issue is pretty clear since one of my components is updating another after a specific action is done. However, not sure why would this throw a warning. Any suggestion on how to get around this?

@gaearon thanks for the details on how to debug from the stack, I personally wouldn't have been able to figure out where this error was coming from if you had not given that example. 🙏

Not sure if my issue is related however, I'm attempting to update the state of my form component but whenever I try to add an onChange handler, it keeps giving me this error. Mind you, I'm using react-jsonschema-form and imported the Form component and I'm using it's onChange property to update the state..

For me, this is the pattern that causes the issue.

image

There may be a way around this. But the console log did point me straight to line 385

I'm new to react but I had code like this:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

Which resulted in: Cannot update a component from inside the function body of a different component.

All I had to do is add an arrow function to setMobileNavOpen in MobileNav like so:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

And that fixes the problem, hope this helps someone!

I'm new to react but I had code like this:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={setMobileNavOpen(false)} //problem here
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

Which resulted in: Cannot update a component from inside the function body of a different component.

All I had to do is add an arrow function to setMobileNavOpen in MobileNav like so:

import React, { useState } from "react";

function Home() {
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(true)}
        type="button"
        className="btn"
      >
        X
      </button>
      {mobileNavOpen && <MobileNav setMobileNavOpen={setMobileNavOpen}/>}
    </div>
  );
}

function MobileNav() {
  return (
    <div>
      <button
        onClick={(): void => setMobileNavOpen(false)} //fixes problem
        type="button"
        className="btn"
      >
        X
      </button>
    </div>
  );
}

export default Home;

And that fixes the problem, hope this helps someone!

Your example is actually one of the early mistakes people make with react. I'm not sure it's exactly the same issue that's being discussed here. Your line here: onClick={setMobileNavOpen(false) is calling the function during the button tender, not on click. That's why wrapping it in a arrow function fixes it.

I ran into this issue with React Router where I needed to dispatch a Redux action before <Redirect>ing the user to a different location. The problem seemed to be that the redirect happened before the dispatch had finished. I fixed the problem by promisifying my action.

Before:

<Route
  render={routeProps => {
    setRedirectionTarget(somePath(routeProps));
    return <Redirect to={someOtherPath} />;
  }}
/>;

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: (target: string | null) => dispatch(setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): SetRedirectionTarget => {
  return {
    type: SET_REDIRECTION_TARGET,
    location
  };
};

After:

function mapDispatchToProps(dispatch: ThunkDispatch) {
  return {
    setRedirectionTarget: async (target: string | null) => dispatch(await setRedirectionTarget(target))
  };
}

export const setRedirectionTarget = (location: string | null): Promise<SetRedirectionTarget> => {
  return Promise.resolve({
    type: SET_REDIRECTION_TARGET,
    location
  });
};

I think it would make to make the error message include names for both the component that is currently rendering, and the component whose setState is being called. Does anyone want to send a PR for this?

I think it would make to make the error message include names for both the component that is currently rendering, and the component whose setState is being called. Does anyone want to send a PR for this?

I'm happy to take a look at this. Any pointers I should be aware of?

@samcooke98 I've opened a PR for this https://github.com/facebook/react/pull/18316

As others have pointed out you can run into this issue if you having a subscription in a hook such as with Apollo and try and update a store on receiving data. Simple fix is to use useEffect e.g.

export const useOrderbookSubscription = marketId => {
  const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
    variables: {
      marketId,
    },
  })

  const formattedData = useMemo(() => {
    // Don't dispatch in here.
  }, [data])

  // Don't dispatch here either

  // Dispatch in a useEffect
  useEffect(() => {
    orderbookStore.dispatch(setOrderbookData(formattedData))
  }, [formattedData])

  return { data: formattedData, error, loading }
}

We were facing this issue in Hyper but we are not using hooks, and couldn't find anything in the render call from the call stack. But there was a call in UNSAFE_componentWillReceiveProps in the stack. Updating that with componentDidUpdate fixed the issue for us https://github.com/zeit/hyper/pull/4382
Posted it here if it might help someone

Same here, there was a UNSAFE_componentWillMount call, changing/removing it fixed the issue

but we are not using hooks, and couldn't find anything in the render call from the call stack

This sounds strange. I'm not sure how you're getting this warning then. It only fires if the setState belongs to a function component. What's your stack like?

but we are not using hooks, and couldn't find anything in the render call from the call stack

This sounds strange. I'm not sure how you're getting this warning then. It only fires if the setState belongs to a function component. What's your stack like?

Class components with Redux, and functions which apply plugins to the components. Maybe that's why it's counted as a function component? But then why does updating the lifecycle hooks fix it?

I’m not sure. Can you try to create an isolated example in a sandbox? I have a hunch you might be doing something else unexpected.

Not sure if I'd able to replicate it in an isolated example. I had a hard time trying to find the cause, and had just updated the lifecycle hook as it was in the stack trace and was pending for update. That somehow fixed the issue.
You can take a look at the repo at the time it had the issue here

Can it be because of the fact that both UNSAFE_componentWillReceiveProps and componentDidUpdate were in the component at that time (unsafe one was left in there mistakenly)?

I'm also getting this warning, and the stack trace points to the setScriptLoaded hook (see below for my custom hook). So it appears that even though I'm using useEffect, React will still throw a warning if the setState handler is nested within other async callbacks (in my case, it's nested in a setTimeout and a load event handler)? My first time using Hooks, so I would appreciate any advice. Thank you!

/**
 * Detect when 3rd party script is ready to use
 * 
 * @param {function} verifyScriptLoaded Callback to verify if script loaded correctly
 * @param {string} scriptTagId 
 */

export const useScriptLoadStatus = (verifyScriptLoaded, scriptTagId) => {
    let initLoadStatus = true; // HTML already includes script tag when rendered server-side
    if (__BROWSER__) {
        initLoadStatus = typeof verifyScriptLoaded === 'function' ? verifyScriptLoaded() : false;
    }
    const [isScriptLoaded, setScriptLoaded] = useState(initLoadStatus); 

    useEffect(() => {
        if (!isScriptLoaded) {
            // need to wrap in setTimeout because Helmet appends the script tags async-ly after component mounts (https://github.com/nfl/react-helmet/issues/146)
            setTimeout(() => {
                let newScriptTag = document.querySelector(`#${scriptTagId}`);
                if (newScriptTag && typeof verifyScriptLoaded === 'function') {
                    newScriptTag.addEventListener('load', () => { 
                        return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                    });
                    // double check if script is already loaded before the event listener is added
                    return verifyScriptLoaded() ? setScriptLoaded(true) : null;
                }
            }, 100);
        }
    });

    return isScriptLoaded;
};

@LabhanshAgrawal

Can it be because of the fact that both UNSAFE_componentWillReceiveProps and componentDidUpdate were in the component at that time (unsafe one was left in there mistakenly)?

I don't think any lifecycle methods are relevant here at all. This is why I'm saying that something is strange in your example.

@suhanw Please provide a complete example in CodeSandbox. I don't see any problem with your code that should be causing this warning.

@LabhanshAgrawal Can you post your full stack please? I think what may be happening is that your UNSAFE_componentWillReceiveProps (which is equivalent to rendering) calls setState on another component.

backend.js:6 Warning: Cannot update a component from inside the function body of a different component.
    in Term                           (created by _exposeDecorated(Term))
    in _exposeDecorated(Term)         (created by DecoratedComponent)
    in DecoratedComponent             (created by TermGroup_)
    in TermGroup_                     (created by ConnectFunction)
    in ConnectFunction                (created by Connect(TermGroup_))
    in Connect(TermGroup_)            (created by _exposeDecorated(TermGroup))
    in _exposeDecorated(TermGroup)    (created by DecoratedComponent)
    in DecoratedComponent             (created by Terms)
    in div                            (created by Terms)
    in div                            (created by Terms)
    in Terms                          (created by _exposeDecorated(Terms))
    in _exposeDecorated(Terms)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)    (created by Hyper)
    in div                            (created by Hyper)
    in div                            (created by Hyper)
    in Hyper                          (created by _exposeDecorated(Hyper))
    in _exposeDecorated(Hyper)        (created by DecoratedComponent)
    in DecoratedComponent             (created by ConnectFunction)
    in ConnectFunction                (created by Connect(DecoratedComponent))
    in Connect(DecoratedComponent)
    in Provider
r                                     @ backend.js:6
printWarning                          @ react-dom.development.js:88
error                                 @ react-dom.development.js:60
warnAboutRenderPhaseUpdatesInDEV      @ react-dom.development.js:23260
scheduleUpdateOnFiber                 @ react-dom.development.js:21196
dispatchAction                        @ react-dom.development.js:15682
checkForUpdates                       @ connectAdvanced.js:88
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
checkForUpdates                       @ connectAdvanced.js:77
handleChangeWrapper                   @ Subscription.js:97
(anonymous)                           @ Subscription.js:23
batchedUpdates$1                      @ react-dom.development.js:21887
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ sessions.ts:124
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
onResize                              @ terms.ts:62
(anonymous)                           @ term.js:179
e.fire                                @ xterm.js:1
t.resize                              @ xterm.js:1
e.resize                              @ xterm.js:1
e.fit                                 @ xterm-addon-fit.js:1
fitResize                             @ term.js:291
UNSAFE_componentWillReceiveProps      @ term.js:408
callComponentWillReceiveProps         @ react-dom.development.js:12998
updateClassInstance                   @ react-dom.development.js:13200
updateClassComponent                  @ react-dom.development.js:17131
beginWork                             @ react-dom.development.js:18653
beginWork$1                           @ react-dom.development.js:23210
performUnitOfWork                     @ react-dom.development.js:22185
workLoopSync                          @ react-dom.development.js:22161
performSyncWorkOnRoot                 @ react-dom.development.js:21787
(anonymous)                           @ react-dom.development.js:11111
unstable_runWithPriority              @ scheduler.development.js:653
runWithPriority$1                     @ react-dom.development.js:11061
flushSyncCallbackQueueImpl            @ react-dom.development.js:11106
flushSyncCallbackQueue                @ react-dom.development.js:11094
batchedUpdates$1                      @ react-dom.development.js:21893
notify                                @ Subscription.js:19
notifyNestedSubs                      @ Subscription.js:92
handleChangeWrapper                   @ Subscription.js:97
dispatch                              @ redux.js:222
e                                     @ VM64:1
(anonymous)                           @ effects.ts:11
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
effect                                @ ui.ts:60
(anonymous)                           @ effects.ts:13
(anonymous)                           @ write-middleware.ts:14
(anonymous)                           @ index.js:11
(anonymous)                           @ plugins.ts:538
(anonymous)                           @ plugins.ts:540
(anonymous)                           @ index.js:11
dispatch                              @ redux.js:638
(anonymous)                           @ ui.ts:54
(anonymous)                           @ index.js:8
dispatch                              @ VM64:1
(anonymous)                           @ index.tsx:162
emit                                  @ events.js:210
(anonymous)                           @ rpc.ts:31
emit                                  @ events.js:210
onMessage                             @ init.ts:50

@gaearon appreciate the quick response. I'll figure out a way to create an example in CodeSandbox (it's challenging), but in the meantime, this is my full stack trace

the line that points to my custom hook is this: https://gist.github.com/suhanw/bcc2688bba131df8301dae073977654f#file-stack-trace-L144

it would be awesome if you could take a look and let me know if there is anything in the stack trace before/after my custom hook that i should investigate further. Thanks!

@LabhanshAgrawal In your stack, UNSAFE_componentWillReceiveProps calls some fitResize which dispatches a Redux action, which in turn updates a bunch of components. Hence the problem. So yeah, changing that to componentDidUpdate works.

@suhanw In your stack, something called ModuleImpressionTracker appears to dispatch a Redux action during a constructor. Constructors shouldn't have side effects. I think that's the cause of the issue, not your Hook.

I see, so I take away from this that UNSAFE_componentWillReceiveProps is counted as rendering but componentDidUpdate isn't.
Can you clarify a bit on it being an issue with Functional components and hooks doing setState, couldn't get it clearly from the discussion above.
Apologies if the question is a bit silly or my take away is wrong, I'm a bit new to this.

@LabhanshAgrawal

Can you clarify a bit on it being an issue with Functional components and hooks doing setState, couldn't get it clearly from the discussion above.

Honestly I'm not sure myself because of all the Redux stuff in the middle. It's pretty confusing due to how React Redux is implemented. It would be nice if someone could get a clear repro that involves React alone but uses class components.

Still seems like there's lots of stack traces being pasted, and not many actual repros of the actual issue regardless of type.

I guess the one for urql is intended @gaearon so what happens is:

  • We have our <Todos /> mounted, which fetches the data and renders it
  • The previous set up a stream connected to the urqlClient
  • We render our second <Todos /> this will produce the same query + variables combination so will refresh the result for <Todos /> of the first step.
  • use-subscription is triggered for both.

Repro - Press "open" at the top when the first query renders.

We could queue up updates but as long as we don't do top-down renders with context we won't be able to circumvent this issue I assume? In theory this is intended behavior for this case. Curious how Relay works around this.

EDIT: we might have found a solution for the case of urql by not triggering all subscribers to update during the getCurrentValue of a use-subscription

https://github.com/FormidableLabs/urql/commit/3a597dd92587ef852c18139e9781e853f763e930

OK so looking deeper into this, I think we're warning in more cases (e.g. for classes) than we originally intended. We'll look into silencing some of those.

@JoviDeCroock This example isn't super helpful because I have no idea what urql does. :-) If you want feedback on a pattern, could you prepare a sandbox demonstrating that pattern in isolation?

@JoviDeCroock Yes, getCurrentValue is definitely not intended to have side effects. We thought that name is pretty explicit about that.

I had an issue where I was getting this warning when dispatching a redux action inside the root scope of a custom hook.

function useCustomHook() {
   const dispatch = useDispatch()
   const value = useSomeOtherHook()
   dispatch(action(value))
}

I fixed this by wrapping the dispatch in a useEffect.

@Glinkis That sounds like a bad pattern regardless. Why would you want to notify the whole tree whenever a component has rendered?

@gaearon yes, the problem we were trying to solve with that is better explained here and seems quite common.

@Glinkis That sounds like a bad pattern regardless. Why would you want to notify the whole tree whenever a component has rendered?

I need to keep my redux state synced with the IDs for my route, so I dispatch this update when my route changes.

I know this may be be suboptimal, but I've found no other way to access the routing data inside my selectors.

@Glinkis Where is the route data coming from?

@JoviDeCroock I think our latest recommendation for that pattern is a custom scheduled garbage collection pass.

@Glinkis Where is the route data coming from?

Not sure if this belongs I this discussion, but this is my hook.

export function useOpenFolder() {
  const dispatch = useDispatch()
  const match = useRouteMatch('/:workspace/(super|settings)?/:type?/:id?/(item)?/:item?')
  const { id, item } = match?.params || {}

  useEffect(() => {
    dispatch(
      openFolder({
        id: id || '',
        item: item || '',
      })
    )
  }, [dispatch, item, id])
}

I later use this state for selectors such as:

export const getActiveItem = createSelector(
  [getActiveFolderItem, getItems],
  (activeItem, items) => items.all[activeItem]
)

@Glinkis Yeah maybe let's wrap it up here but my suggestion would be to read useRouteMatch in the parent component, pass the ID as a prop, and then read props in selector like you normally would. (I don't actually know how this is done in Redux these days but there must be some way.)

I see, so I take away from this that UNSAFE_componentWillReceiveProps is counted as rendering but componentDidUpdate isn't.

@LabhanshAgrawal That's correct. Some background explanation here:

Conceptually, React does work in two phases:

  • The render phase determines what changes need to be made to e.g. the DOM. During this phase, React calls render and then compares the result to the previous render.
  • The commit phase is when React applies any changes. (In the case of React DOM, this is when React inserts, updates, and removes DOM nodes.) React also calls lifecycles like componentDidMount and componentDidUpdate during this phase.

The commit phase is usually very fast, but rendering can be slow. For this reason, the upcoming concurrent mode (which is not enabled by default yet) breaks the rendering work into pieces, pausing and resuming the work to avoid blocking the browser. This means that React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption).

Render phase lifecycles include the following class component methods:

  • constructor
  • componentWillMount (or UNSAFE_componentWillMount)
  • componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
  • componentWillUpdate (or UNSAFE_componentWillUpdate)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState updater functions (the first argument)

Because the above methods might be called more than once, it's important that they do not contain side-effects. Ignoring this rule can lead to a variety of problems, including memory leaks and invalid application state. Unfortunately, it can be difficult to detect these problems as they can often be non-deterministic.

OK so looking deeper into this, I think we're warning in more cases (e.g. for classes) than we originally intended. We'll look into silencing some of those.

I guess even if it's more than intended, in my opinion it's a good thing to have as it helps in improving our projects using classes.

This self-explanatory error message "Cannot update a component from inside the function body of a different component"

That means execute as a function instead of calling component.

example like this :

const App = () => {  
  const fetchRecords = () => { 
    return <div>Loading..</div>;
  };  
  return fetchRecords() // and not like <FetchRecords /> unless it is functional component.
};
export default App;

@rpateld I don't think the example you're showing is related to this warning.

https://github.com/facebook/react/pull/18330 will solve the cases that we didn't intend to start firing.

I am also facing this issue with react@experimental+react-redux+redux.
image

Code looks like this:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
    resource.read()
    return <h1>cabinet</h1>
}

Cabinet.propTypes = {
    resource: PropTypes.shape({
        read: PropTypes.func
    })
}

const CabinetPage = ({
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet
}) => {
    if (tokensRefreshFailed || failedToLoad) {
        clearTokens()
        clearCabinet()
        logout()
        return <Redirect to='/login' />
    }

    return (
        <Suspense fallback={<CircularProgress />}>
            <Cabinet resource={loadCabinet()} />
        </Suspense>
    )
}

CabinetPage.propTypes = {
    loadCabinet: PropTypes.func,
    failedToLoad: PropTypes.bool,
    tokensRefreshFailed: PropTypes.bool,
    logout: PropTypes.func,
    clearTokens: PropTypes.func,
    clearCabinet: PropTypes.func
}

const mapStateToProps = ({
    alert,
    tokens: { tokensRefreshFailed },
    cabinet: { failedToLoad }
}) => ({
    alert,
    tokensRefreshFailed,
    failedToLoad
})

const mapDispatchToProps = dispatch =>
    bindActionCreators(
        {
            cabinetLoad: cabinetActions.load,
            logout: userActions.logoutWithoutRedirect,
            loadCabinet: api.loadCabinet,
            clearCabinet: cabinetActions.clear,
            clearTokens: tokensActions.clear
        },
        dispatch
    )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet() dispatches an async action of three-phase rendering as concurrent docs say, and returns an object with read() prop.
However, I can't see any parent updates here.

@h0tw4t3r You are dispatching a Redux action during a component's render. This is not supported, and that's what the warning is about. It's best to ask React Router experts about how to handle this case (redirect) gracefully, can't help with that part.

Regarding concurrent mode, please note Redux is not currently compatible with it in general. So you might want to avoid it if you're experimenting with CM.

 Small update on this thread

We're going to release a patch of React soon that fixes over-firing of this warning for classes. So if you experience this, consider waiting a few days and then trying 16.13.1 when it's out.

If you want to keep looking for the causes, I hope https://github.com/facebook/react/issues/18178#issuecomment-595846312 explains how.

I find it very strange that same logic when used in a class component does not give any warnings while functional (hooks) does:

Functional component (Hooks):

import React, { Component } from "react"
import SortableTree from "react-sortable-tree"
import "react-sortable-tree/style.css"

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
]

const nodeInfo = row => console.log(row)

export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    }
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1))

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      })

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      })

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault()
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    â„č
                  </button>
                ]
              }
            }}
          />
        </div>
      </div>
    )
  }
}

image

Class component:

import React from "react";
import SortableTree from "react-sortable-tree";
import "react-sortable-tree/style.css";

const data = [
  {
    title: "Windows 10",
    subtitle: "running",
    children: [
      {
        title: "Ubuntu 12",
        subtitle: "halted",
        children: [
          {
            title: "Debian",
            subtitle: "gone"
          }
        ]
      },
      {
        title: "Centos 8",
        subtitle: "hardening"
      },
      {
        title: "Suse",
        subtitle: "license"
      }
    ]
  }
];

const nodeInfo = row => console.log(row);

export default class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      searchString: "",
      searchFocusIndex: 0,
      searchFoundCount: null,
      treeData: data
    };
  }

  render() {
    const { searchString, searchFocusIndex, searchFoundCount } = this.state;

    const customSearchMethod = ({ node, searchQuery }) =>
      searchQuery &&
      ((node.title &&
        node.title.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1) ||
        (node.subtitle &&
          node.subtitle.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1));

    const selectPrevMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
            : searchFoundCount - 1
      });

    const selectNextMatch = () =>
      this.setState({
        searchFocusIndex:
          searchFocusIndex !== null
            ? (searchFocusIndex + 1) % searchFoundCount
            : 0
      });

    return (
      <div>
        <h2>Find the needle!</h2>
        <form
          style={{ display: "inline-block" }}
          onSubmit={event => {
            event.preventDefault();
          }}
        >
          <input
            id="find-box"
            type="text"
            placeholder="Search..."
            style={{ fontSize: "1rem" }}
            value={searchString}
            onChange={event =>
              this.setState({ searchString: event.target.value })
            }
          />
          &nbsp;
          <button
            type="button"
            disabled={!searchFoundCount}
            onClick={selectPrevMatch}
          >
            &lt;
          </button>
          &nbsp;
          <button
            type="submit"
            disabled={!searchFoundCount}
            onClick={selectNextMatch}
          >
            &gt;
          </button>
          &nbsp;
          <span>
            &nbsp;
            {searchFoundCount > 0 ? searchFocusIndex + 1 : 0}
            &nbsp;/&nbsp;
            {searchFoundCount || 0}
          </span>
        </form>

        <div style={{ height: 300 }}>
          <SortableTree
            treeData={this.state.treeData}
            onChange={treeData => this.setState({ treeData })}
            searchMethod={customSearchMethod}
            searchQuery={searchString}
            searchFocusOffset={searchFocusIndex}
            searchFinishCallback={matches =>
              this.setState({
                searchFoundCount: matches.length,
                searchFocusIndex:
                  matches.length > 0 ? searchFocusIndex % matches.length : 0
              })
            }
            generateNodeProps={row => {
              return {
                title: row.node.title,
                subtitle: (
                  <div style={{ lineHeight: "2em" }}>{row.node.subtitle}</div>
                ),
                buttons: [
                  <button
                    type="button"
                    className="btn btn-outline-success"
                    style={{
                      verticalAlign: "middle"
                    }}
                    onClick={() => nodeInfo(row)}
                  >
                    â„č
                  </button>
                ]
              };
            }}
          />
        </div>
      </div>
    );
  }
}

@radulle This isn't a very helpful example by itself. I tried putting it into CodeSandbox but it doesn't work: https://codesandbox.io/s/clever-taussig-9xixs. Can you prepare an example that we can try?

@gaearon I wanted to create a codesandbox but the latest version of the library has some issues with it. Error is not thrown in the old versions. At the moment it seems that the only way to reproduce it is to spin it up in Create React App locally :(

@radulle What version can I try that would work on CodeSandbox?

@gaearon 2.6.2 works and throws the error/warning with this setup:
image
So for the same setup:
Functional component: errors/warnings
Class component: no errors/warnings
Maybe I'm missed something and they are not equivalent.

Yeah, this is one of the cases I referred to in https://github.com/facebook/react/issues/18178#issuecomment-600369392. We'll mute the warning in this case. The warning itself is legit and as you rightly say, conceptually it's a problem in classes too. However, the discrepancy doesn't make sense so we'll mute it for both cases for now when it's coming from a class (which it is, in this example).

I believe there is a legitimate use case for firing state updates from the render function and not from an effect, and that is to preserve the execution order.

To illustrate with a practical example: in our app, we have a peculiar system for managing breadcrumbs.

  • We have a global context that holds all breadcrumbs
  • We have a hook that allows us to add breadcrumbs to this context, like this:

    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb>{item.name}</Breadcrumb>, [item.name]);
    
  • We have a component that reads the information from the context, and renders all breadcrumbs. So, whenever we want to render the breadcrumbs, we simply have to call it with no params.

This is very practical, because we don't have to maintain any breadcrumb structure: this structure is declared in the code itself. If we want to move a route to another part of the application, the breadcrumb system will keep working.

So, combined with react-router, we can do something like this:

// Main/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Home from './Home';
import Movies from './Movies';

const Main = () => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to="/">Home</Breadcrumb>, []);

    return <Switch>
        <Route path="/movies">
            <Movies />
        </Route>
        <Route path="/" />
            <Home />
        </Route>
    </Switch>
}

// Movies/index.tsx
import { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import Detail from './Detail';
import Master from './Master';

const Movies = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    addBreadcrumb(<Breadcrumb to={url}>Movies</Breadcrumb>, [url]);

    return <Switch>
        <Route path="/:id">
            <Detail />
        </Route>
        <Route path="/" />
            <Master />
        </Route>
    </Switch>
}

// Movies/Detail/index.tsx
import Breadcrumbs, { useAddBreadcrumb } from 'components/Breadcrumbs';
import React from 'react';
import { useRouteMatch } from 'react-router-dom';

const MovieDetail = ({ url }) => {
    const addBreadcrumb = useAddBreadcrumb();
    const { params: { id } } = useRouteMatch<{ id: string; }>();
    const movie = useMovie(id);

    addBreadcrumb(
        <Breadcrumb to={url}>{movie?.name}</Breadcrumb>,
        [movie?.name, url]
    );

    return <div>
        <Breadcrumbs />
    </div>
}

Now, if we go to /movies/gone-with-the-wind, our breadcrumbs will look like:

Home > Movies > Gone with the wind

Now, here's my point: in order for that to work, we need the execution order to be guaranteed. In this case, the execution order is obvious: first Main renders, then it renders its children, which includes Movies, and finally MovieDetail. In this case, the addBreadcrumb call will be executed in the correct order.

Now, the changelog states this:

In the rare case that you intentionally want to change the state of another component as a result of rendering, you can wrap the setState call into useEffect.

This is, indeed, one of the rare cases where we intentionally want to change the state of another component. However, if we do that the changelog suggests and wrap the addBreadcrumb (which in the end is a glorified setState) into useEffect, the execution order isn't guaranteed anymore. All three setStates will be executed after the rendering is finished, which creates a race condition, and breaks our system.

I don't know whether or not this peculiar system is considered an anti-pattern, but to me it makes sense, and I haven't found any simpler alternatives.

So, to conclude, I welcome this new warning, but I think that the optimal solution for us would be to have a way to supress it. Maybe a second optional parameter to setState would do the trick.

@MeLlamoPablo

Relying on render calls being in the order of the siblings or parent/child render order sounds very fragile. React doesn’t actually guarantee this. Children can (and will) re-render without their parents, as well as the other way around. If children are rendered with a delay (eg code splitting) this code would break too. Or if some children are inserted or deleted dynamically. Not to mention, this is pretty bad performance wise because you have so many cascading renders.

I empathise with the problem you’re trying to solve — indeed it doesn’t have an idiomatic solution in React today. We have some ideas about it but a proper solution would need to be pretty different. In the meantime we strongly discourage this workaround.

@gaearon, thanks for your insight. I have one question: is the order of the very first render guaranteed? Because that's all we need to funcion properly (once we know the order of the breadcrumbs, we don't care about the order of the subsequent renders).

It seems logical to me that the order of the first render is guaranteed. How would React know that a parent component has any children otherwise?

About performance of cascading renders, you're absolutely right. I will look for ways to improve our system.

@MeLlamoPablo

I have one question: is the order of the very first render guaranteed? Because that's all we need to funcion properly (once we know the order of the breadcrumbs, we don't care about the order of the subsequent renders).

Not strongly. I think it mostly happens to work in the current version of React, but that may well change in the future. Even today, combined with features like lazy and Suspense it is not guaranteed.

How would React know that a parent component has any children otherwise?

Sibling order is not guaranteed. For parent/child order, you're right parents have to render first; however, React may need to re-render the parent again before it gets to a child, or even after it has rendered the first child but before the second one. Again, once you add features like code splitting, you lose guarantees even faster.

This is fragile.

@gaearon, thanks again. This is very much appreciated.

Maybe there should be an ESLint rule that warns against calling useState mutators within a render body?

Calling setState of your own component during render is a supported pattern (although it should be used sparingly). It's setState on other components that are bad. You can't detect those statically.

It could theoretically be done using ts-eslint, assuming they are using the correct upstream React types, but yeah, probably more effort than it's worth.

I don't think it could be done without some sort of effect tracking. As soon as you have one function in the middle, you lose the information.

I am also facing this issue with react@experimental+react-redux+redux.
image

Code looks like this:

import React, { Suspense } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import PropTypes from 'prop-types'
import { userActions, cabinetActions, tokensActions } from '../../actions'
import { CircularProgress } from '@material-ui/core'
import { api } from './api'

const Cabinet = ({ resource }) => {
  resource.read()
  return <h1>cabinet</h1>
}

Cabinet.propTypes = {
  resource: PropTypes.shape({
      read: PropTypes.func
  })
}

const CabinetPage = ({
  failedToLoad,
  tokensRefreshFailed,
  logout,
  loadCabinet,
  clearTokens,
  clearCabinet
}) => {
  if (tokensRefreshFailed || failedToLoad) {
      clearTokens()
      clearCabinet()
      logout()
      return <Redirect to='/login' />
  }

  return (
      <Suspense fallback={<CircularProgress />}>
          <Cabinet resource={loadCabinet()} />
      </Suspense>
  )
}

CabinetPage.propTypes = {
  loadCabinet: PropTypes.func,
  failedToLoad: PropTypes.bool,
  tokensRefreshFailed: PropTypes.bool,
  logout: PropTypes.func,
  clearTokens: PropTypes.func,
  clearCabinet: PropTypes.func
}

const mapStateToProps = ({
  alert,
  tokens: { tokensRefreshFailed },
  cabinet: { failedToLoad }
}) => ({
  alert,
  tokensRefreshFailed,
  failedToLoad
})

const mapDispatchToProps = dispatch =>
  bindActionCreators(
      {
          cabinetLoad: cabinetActions.load,
          logout: userActions.logoutWithoutRedirect,
          loadCabinet: api.loadCabinet,
          clearCabinet: cabinetActions.clear,
          clearTokens: tokensActions.clear
      },
      dispatch
  )

export default connect(mapStateToProps, mapDispatchToProps)(CabinetPage)

loadCabinet() dispatches an async action of three-phase rendering as concurrent docs say, and returns an object with read() prop.
However, I can't see any parent updates here.

For anyone else having this issue, I fixed it by moving redux action dispatching to a returned component. Here's how it looks like now:

const CabinetPage = ({
    alert,
    failedToLoad,
    tokensRefreshFailed,
    logout,
    loadCabinet,
    clearTokens,
    clearCabinet,
    clearAlert
}) => (
    <Suspense fallback={<MUIBackdropProgress />}>
        {alert.message && (failedToLoad || tokensRefreshFailed) ? (
            <MUIAlertDialog
                title={alert.message}
                text={errorText}
                onClose={() => {
                    clearAlert()
                    clearCabinet()
                    clearTokens()
                    logout()
                }}
            />
        ) : (
            <Cabinet resource={loadCabinet()} />
        )}
    </Suspense>
)

I like this warning because it forces you to choose right design patterns. Now everything works flawlessly!

We fixed the cases where the warning was over-firing in 16.13.1. The remaining cases are legit and need to be fixed.

@gaearon just upgraded and the error disappeared! Thank you guys for your work!

@gaearon thanks. you just saved my day :-)

While upgrading did not resolve my issue, it did give me more information in the console to help find my problem. Thanks @gaearon !

What if you dispatch an action that causes the other component to return the same state as last time? Is that considered bad? I would think it would short-circuit in that case.

Can I just say that this while I totally understand the logic behind this warning ... it almost feels like a betrayal of what the React team has been telling the community, because it feels like the team has taught these important truths about how to code React:

1) keep your state as high as you need to in your hierarchy (no higher), and then pass data and setters down to child components

2) Function components are awesome! Forget that class noise, do your whole component in one function!

And now when people follow those two rules, and pass their state setters down to their function components, and call them in those components ... you pull the rug out and go "ha, but you can't actually do what we told you to do".

None of this changes anything about the technical needs here, and I'm not saying anything about this change is wrong or bad ... I just feel there's a messaging problem, in that you're not communicating good clear rules (like the two I just mentioned) for coding in this new world. If you're going to change the rules on us, I think it would be helpful to do so by first explaining best practices.

So, all I'm really asking is ... I think it would be more ideal if someone on the team were to write something (like an article) where they say "I know we gave you those two rules before: here are the new rules", and then add a link to what that article in every place in the docs/release notes that reference this new warning (so everyone googling "WTF is this?" can learn the proper way to code React, in the "new world").

@machineghost : I think you're misunderstanding what the message is warning about.

There's nothing wrong with passing callbacks to children that update state in parents. That's always been fine.

The problem is when one component queues an update in another component, _while the first component is rendering_.

In other words, don't do this:

function SomeChildComponent(props) {
    props.updateSomething();
    return <div />
}

But this is fine:

function SomeChildComponent(props) {
    // or make a callback click handler and call it in there
    return <button onClick={props.updateSomething}>Click Me</button>
}

And, as Dan has pointed out various times, queuing an update in the _same_ component while rendering is fine too:

function SomeChildComponent(props) {
  const [number, setNumber] = useState(0);

  if(props.someValue > 10 && number < 5) {
    // queue an update while rendering, equivalent to getDerivedStateFromProps
    setNumber(42);
  }

  return <div>{number}</div>
}

Right, but when you're coding your component you're not thinking of the timing of its parent. That's part of the beauty of React components: encapsulation.

So again, I'm not saying the new warning is bad at all, I'm saying before we had two rules that any good React dev could follow. Now under X conditions those rules go out the window, but only under X (where it sounds like X = "while the parent component is also updating").

I just think there needs to be more focus on explaining that, and on how to avoid the problem, instead of just being like "this is a problem now!".

@machineghost : you're _truly_ not getting what I'm saying here.

Parent/child timing is not the issue.

Queueing state updates _in other components while rendering a function component_ is the issue.

By definition it has to be a parent/child (or grandchild): how else could it have passed the state setter down?

I'm not saying this can't also be an issue in other component relationships, but I'm specifically talking about people following React best practices, passing state setters down, and then getting this warning.

That's all I'm talking about, and all I'm saying is, "it could be explained better, with more focus on how to code well instead of just 'here's a new error and here's what it means'".

Timing. Is. Not. The. Issue.

A function component is allowed to queue a state update, while rendering, for itself only. As my example showed, that acts as the equivalent of getDerivedStateFromProps.

Queueing an update for _any_ other component from within the actual rendering body of a function component is illegal.

That's what this warning is telling you.

I don't know how I can explain that any clearer.

Timing is not the issue: not your's, not mine. My issue is documentation, or lack thereof.

But you've decided to start a war in an issue thread with an Internet stranger, instead of listening to what they're saying and ... I have no desire to continue to engage with you.

The point is that no rules have changed. This has always been a wrong pattern. It's just now being highlighted so that you're aware your code is buggy.

And literally nothing you just said disagrees with anything I wrote. In fact, it's almost like I've said the exact same thing from the start ... and all I ever asked for this whole time was a better explanation of those same rules, the ones you say haven't changed (and of course they haven't ... what changed and "made a new world" was the warning).

P.S. You also seem to fail to realize the irony here. If I fail to understand anything, that's making the case that the documentation could be improved. Yelling at me about how poorly I understand things only strengthens my position; it doesn't magically improve the documentation.

Hi folks, let’s cool down a bit. 🙂

@markerikson Appreciate you jumping in but I think this discussion is getting a bit too heated.

@machineghost Thanks for expressing your concerns. I know it’s annoying when new warnings pop up for patterns that might have seemed innocuous before.

I agree this warning requires some prior context. Essentially, you needed to know two things from the class era:

  • That you shouldn’t setState during render. Classes always warned about this.

  • That function component body is essentially the same thing as class component render method.

It is indeed our omission that setState on another component during the function component body did not warn before. You could infer it’s a bad pattern from the two points above but it’s fair to say one could not realize it. We’re sorry for the inconvenience.

If you feel there’s any particular place in the docs where this should be mentioned, please raise an issue on the docs repository. We’re planning a rewrite of the docs to be based around Hooks so that’s something we can keep in mind. Thanks!

I in no way mean to make anyone feel bad about anything, and I refuse to accept your apology ;) Hooks are genius, and y'all are geniuses for inventing them. And any engineer who faults another engineer for not perfectly imagining every outcome is ... a jerk.

All I've been trying to communicate is, currently when I got this warning, I did what everyone does: I googled it. I then found a page that said "we've got this new warning".

I just think it would have been better (and could still be better) if there could have been (can be) a link in that announcement, or similar places that someone might find by googling, to some "higher level discussion" of "here's why we introduced this error and here's how you can be an awesome React dev and follow some basic guidelines to never run into it yourself."

But again, hooks are awesome, the React team is awesome, and even this new warning is awesome (I'm sure it beats the hell out of discovering the error it's trying to guard against). If anyone owes anyone an aplogoy it's me for not leading with that.

Sure, no hard feelings. The answer to “why” isn’t any more complex than “if one component triggers updates on other components during rendering, it becomes very hard to trace the data flow of your app because it no longer goes from top down”. So if you do that you’re kind of throwing away the benefits of React.

Again, to clarify, this isn’t new per se — classes always had the same warning. We missed it with Hooks, and are rectifying the mistake. I suppose that the way you fix it depends on the case — so we can’t give you exact instructions — but happy to help if you share an example you’re struggling with. Generally saying, you’d fix it in a similar way to how you’d fix the corresponding class warning that always existed.

Hope that helps somewhat!

I will add my two cents to this discussion and agree with @machineghost that there has been a lot of confused since the introduction of functional components and hooks. The community is looking to the react team for leadership but things that used to be simple are becoming convoluted and there seems to be a lack of communication and clear examples.

Case and point being ComponentDidMount and Unmount, first we're told use function components, then use useEffect with an empty array, then we're told that's no good, now we've got this error message mess. I don't know, I appreciate all the hard work going into react but we really need more effort put into documentation and best practices.

I've been on the function bandwagon for so long (trying to avoid classes with Recompose and such even before hooks) that I don't even remember those class warnings.

And while I appreciate your response, I was mainly just hoping for "rules of thumb", guidelines, best practices, etc. Stuff like "keep your state as high as it needs to be to cover all components that use it, but no higher" or "pass state setters down to child components using the inversion of control pattern".

Maybe there just aren't any here, but maybe something like "if functional component A changes state, it shouldn't render child component B that it passes a state setter to (it should return right then or something), because then if the child component renders and changes state you'll have problems".

Or maybe it's late on Sunday, I've been working all day on a personal project, and my brain is just too fried and is making something simple into something hard. Either way, I'll post back if I have any further suggestions for guidelines, but otherwise I certainly don't want anyone else spending their Sunday night on this.

Thanks again though!

I think we’re getting to the point where this thread has outlived its usefulness.

Case and point being ComponentDidMount and Unmount, first we're told use function components, then use useEffect with an empty array, then we're told that's no good, now we've got this error message mess.

I’m sorry that our documentation hasn’t helped you but it’s very hard to say what specific problems you ran into. This is unfortunately very vague and is a distortion of what we’ve tried to say. Like a game of broken telephone. If you have a specific problem please file a new issue and try to describe it in more detail. We’ll try to help you if you can be more specific.

And while I appreciate your response, I was mainly just hoping for "rules of thumb", guidelines, best practices, etc

The rule of thumb has always been “don’t perform side effects while rendering”. Think of rendering as a pure computation. Side effects go into a different place (lifecycle methods in classes, or useEffect in function components). There’s not much more to it.

"if functional component A changes state, it shouldn't render child component B that it passes a state setter to (it should return right then or something), because then if child component renders and changes state you'll have problems".

I think there’s still some misunderstanding here. It’s perfectly fine to pass state setters to a child component. Has always been fine, and will always be.

The problem is in calling them while rendering. That should generally be completely unnecessary. It’s hard for me to guess why you’re doing it without a concrete example. So it’s hard to help.

The general theme in both of these conversations is that we’re going in circles. The discussion has switched to be meta, and instead of talking about specific cases we’re discussing vague generalities. It is likely we misunderstand each other, but the lack of concrete examples is making this misunderstanding impossible to resolve.

For this reason I’m going to lock this thread. I very much appreciate everybody’s input here, and we’d love to hear more from you if you struggle to fix this warning. The way to get help is to file an issue with a minimal reproducing example. Then we can discuss your concrete problem and help brainstorm a solution. This will be more productive for everyone involved, and will also avoid sending emails to dozens of people who have already commented on this thread and ended up subscribing. Thank you!

As a small update (sorry for pinging everyone), I heard https://github.com/final-form/react-final-form/issues/751#issuecomment-606212893 resolved a bunch of these errors for people who were using that library.

Was this page helpful?
0 / 5 - 0 ratings