Recoil: Warning: Cannot update a component (`xxx`) while rendering a different component (`xxx`).

A warning is occured where useRecoilValue() is used even though I write a code like one which is in the official document here.

スクリーンショット 2020-05-15 4 55 26

this is my code

import React, { useEffect } from 'react';
import { atom, useRecoilState, useRecoilValue, selector } from 'recoil';

function RecoilApp() {
  useEffect(() => {
      console.log('test');
  });
  return(
    <div>
      Recoil App
      <TextInput />
      <CharCount />
    </div>
  );
}

const textState = atom({key: 'textState', default: ''});

function TextInput() {
  const [text, setText] = useRecoilState(textState);

  const onChange = event => {
    setText(event.target.value);
  }

  return(
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
};

function CharCount() {
  const count = useRecoilValue(countState);
  return <>Charset Count: {count}</>;
}

const countState = selector({
  key: 'countState',
  get: ({get}) => {
    const text = get(textState);
    return text.length;
  },
});

export default RecoilApp;

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 156
  • Comments: 88 (10 by maintainers)

Commits related to this issue

Most upvoted comments

I’m working on a big refactor where dependencies will not go through React state at all. That will fix this (and make it a lot more efficient, and is needed for CM). In the meantime I don’t see a an obvious workaround unfortunately.

Thanks for reporting, I am investigating this.

Humm, I wonder that the codesandbox occured no warning at console like the image.

You need to try with react v16.13.1. https://codesandbox.io/s/async-pine-gogdb

(edit: I’m surprised to get so many down votes. Many people probably misunderstood my comment is how to fix the warning. My apologies for not clarifying the context. It was a comment for @syunto07ka to “reproduce” the warning in codesandbox.)

The Batcher’s component’s setState is the state setter that is being called, and the main cause is because replaceState from RecoilRoot is called when rendering subscribed components.

The warning was initially added to react on the v16.13.0 release, and highlights an anti-pattern of the react mental model.

Inlining the logic in the batcher’s effect to replaceState solves the issue (along with setTimeout on the notifyBatcherOfChange.current(), but I don’t believe that setTimeout is a reliable solution). While RecoilRoot wraps a context provider, a state inside it could be a solution, but we will return to the initial problem.

I understand that the main goal of the Batcher is to catch as many setState calls from subscribers as possible and delegate to react batching to gather queuedComponentCallbacks and optimize the count of re-renders. Moreover, the subscribed component callback is the forceUpdate (state setter) of useInterface that is linked to subscribed components. So if many updates occur in a short duration, I prefer leaving react to handle the batching stuff rather than building a logic on top of it, and also because it is unstable.

Besides, the replaceState will notify the batcher only with the new state reference change, and as far as I read, this is the case when a new subscription occurs.

My question is, Shouldn’t we drop the batcher and leave react manage the batching of state updates ? I mean, it is like an abstraction over an abstraction, or am I mistaking something?

I think to solve the problem without dropping the Batcher, a work loop that schedules when to flush the subscriptions is mandatory (may have its own problems though).

I found such a workaround, in child component i change parent component state with useEffect:

function Child () {
  const [ref, dimensions] = useDimensions()
  const {setDimensions} = useContext(ParentContext)

  // bad
  setDimensions(dimensions)

  // good
  useEffect(() => {
    setDimensions(dimensions)
  })

  return <div ref={ref}></div>

I had a similar issue and it turns out the problem was how I was using it. I had an async selector which derived a value from an atom.

For example, I had a User atom and a Roles async selector. Whenever the User updated (should really only happen once) then the Roles would be loaded from the API for that User.

However, I used a hook to retrieve the User and every time the hook ran, it updated the User (in the hook), which in turn triggered a Roles API request.

Simple fix was to set the User atom from a useEffect, with the User in the deps array.

Confirming Recoil breaking hmr/fast refresh.

Screen Shot 2020-05-15 at 9 19 01 AM

The error is present in both the codesandbox links

In my case, it’s because I was mutating a prop value in the child component.

BEFORE

import React from 'react';

const Todo = ({ text, todo, todos, setTodos }) => {
  
  const deleteHandler = () => {
    setTodos(todos.filter(el => el.id !== todo.id)) // mutauing a prop here
  }

 return (
   <div className="todo">
     <li className="todo-item">{text}</li>
     <button className="complete-btn"><i className="fas fa-check"></i></button>
     <button className="trash-btn" onClick={deleteHandler()}><i className="fas fa-trash"></i></button>
   </div>
 );
}

export default Todo;

AFTER

import React from 'react';

const Todo = ({ text, todo, todos, setTodos }) => {

  const todosCopy = JSON.parse(JSON.stringify(todos));
  
  const deleteHandler = () => {
    setTodos(todosCopy.filter(el => el.id !== todo.id)) // mutating a copy of prop value here
  }

 return (
   <div className="todo">
     <li className="todo-item">{text}</li>
     <button className="complete-btn"><i className="fas fa-check"></i></button>
     <button className="trash-btn" onClick={deleteHandler}><i className="fas fa-trash"></i></button>
   </div>
 );
}

export default Todo;

Hi @davidmccabe, Any ETA on this?

Inclusion of any selector in any FC whereby the selector calls get on an atom, generates this error. The simplest use case in my project is:

//
// foobarState.ts
//
export const foo = atom<boolean>({ key: "foo", default: true });
export const bar = selector<boolean>({ key: "bar", get: ({ get }) => !get(foo) });
//
// component.tsx
//
...
export default ((props) => {
    const foobar = useRecoilValue(bar);
    return <>Hello World</>
}

As an early adopter with high hopes for this project, I’m migrating away from Redux to Recoil!! Crossing fingers to see Recoil “stable enough” for our beta in a few months.

Thanks for your efforts on this!!

Fixed in v0.0.13 Youhouhou

I have copied and pasted your code and it works. See example in codesandbox

Fix is in the v0.0.11 release so keep watch for that.

Proposed: 15-Sep-2020 (https://github.com/facebookexperimental/Recoil/issues/534#issuecomment-691580341)

Maybe something related to hot-loader? I just had:

Warning: Cannot update a component from inside the function body of a different component.

then I removed the react-dom alias from my webpack config

alias: {
   'react-dom': '@hot-loader/react-dom',
},

and the error changes to

Warning: Cannot update a component (`Batcher`) while rendering a different component (`Header`). To locate the bad setState() call inside `Header`, follow the stack trace as described in https://fb.me/setstate-in-render

Header is my component name…

Looks like for now we need to live (and can) with such warning till Recoil team will provide refactored version of Batcher.

For now the warning is valid and based on adding new validation-warning from React team to highligh anti-pattern to use useState setter out of component which happens in this place. image

Downgrading React version to 16.12 will remove this issue if it annoying.

New warning React 16.13: https://ru.reactjs.org/blog/2020/02/26/react-v16.13.0.html#warnings-for-some-updates-during-render The details: https://github.com/facebook/react/issues/18178#issuecomment-602327720.

imho: any hacks and workaround will lead to complexity increasing and unexpected issues and it looks like a waste of time since the goal of this warning is only notify about the issue. And the fix should be centralized at least in this case.

Surprised there’s been no updates on Recoil since June. I’ve just started using it and it is almost life changing. This pescy warning is annoying and hoping it will be fixed soon?

Same here on React 17.0.2 and recoil 0.3.1.

Is it safe to ignore this warning?

I have this issue and I’m not using react hot module loading.

I’m working on a big refactor where dependencies will not go through React state at all. That will fix this (and make it a lot more efficient, and is needed for CM). In the meantime I don’t see a an obvious workaround unfortunately.

@davidmccabe Any idea on a rough timeline for when we can expect this refactor to be working?

same issue here. Thanks for your work.

Any update for this issue 😦 i try to add this to new project instead of redux …

No, it is not safe to ignore, it is against the model of react itself.

Your component while rendering tries to alter the state of another component, this couples both of them to each other and makes the render operation of your component non atomic and with side effects impacting other components.

Recoil uses some technique internally (for batching if my memory is good enough) that alters the state of components while rendering, and react tells us not to do this, so clearly you should not ignore the message.

AFAIK, it was related to selectors, but I remember only the initial ever code about recoil, I don’t honestly know how this became and if there is a chance to solve this issue.

If I get this warning once and everything else is working as expected, should I be concerned? Does it indicate a memory leak or something else that will affect the performance of the app?

Any updates at this issue?

I’m facing it in my custom IntlProvider. In a nutshell, I’m wrapping it (from react-intl) in a custom component that stores the selected locale in my Recoil state for further usage by my own selectors.

Here is a sample code:

import React, { ReactElement } from 'react'
import { IntlProvider } from 'react-intl'
import { OptionalIntlConfig } from 'react-intl/src/components/provider'
import { useSetRecoilState } from 'recoil'

import { locale as localeAtom } from 'state/recoil/intl/locale'

export interface RecoilIntlProviderProps extends OptionalIntlConfig {
  children: ReactElement
}

const RecoilIntlProvider = (props: RecoilIntlProviderProps): ReactElement => {
  const setRecoilIntl = useSetRecoilState(localeAtom)
  setRecoilIntl(props.locale)

  return <IntlProvider {...props} />
}

export default RecoilIntlProvider

And my atom:

import { atom } from 'recoil'

import { PREFIX } from './constants'

const KEY = `${PREFIX}::LOCALE`

export const locale = atom<string>({
  key: KEY,
  default: '',
})

To make it work, you need to use the following tree in your app.js(ts):

<RecoilRoot>
  <RecoilIntlProvider locale={locale ?? 'pt-BR'} messages={messages}>
    <Component {...pageProps} />
  </RecoilIntlProvider>
</RecoilRoot>

And my warning in the console log:

Warning: Cannot update a component (`Batcher`) while rendering a different component (`RecoilIntlProvider`). To locate the bad setState() call inside `RecoilIntlProvider`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
RecoilIntlProvider@webpack-internal:///./components/Base/RecoilIntlProvider/recoil-intl-provider.tsx:23:87
RecoilRoot@webpack-internal:///../.yarn/$$virtual/recoil-virtual-0c2fe5134c/0/cache/recoil-npm-0.1.2-9a0edbd2b9-c69105dd7d.zip/node_modules/recoil/es/recoil.js:1722:1
BudApp@webpack-internal:///./pages/_app.tsx:70:19
ErrorBoundary@webpack-internal:///../.yarn/$$virtual/@next-react-dev-overlay-virtual-ba69454c0b/0/cache/@next-react-dev-overlay-npm-10.0.0-5db65d7be6-f38cbe7f69.zip/node_modules/@next/react-dev-overlay/lib/internal/ErrorBoundary.js:23:47
ReactDevOverlay@webpack-internal:///../.yarn/$$virtual/@next-react-dev-overlay-virtual-ba69454c0b/0/cache/@next-react-dev-overlay-npm-10.0.0-5db65d7be6-f38cbe7f69.zip/node_modules/@next/react-dev-overlay/lib/internal/ReactDevOverlay.js:73:20
Container@webpack-internal:///../.yarn/$$virtual/next-virtual-6f4174843d/0/cache/next-npm-10.0.0-82dc2f1372-c01b177cb2.zip/node_modules/next/dist/client/index.js:173:20
AppContainer@webpack-internal:///../.yarn/$$virtual/next-virtual-6f4174843d/0/cache/next-npm-10.0.0-82dc2f1372-c01b177cb2.zip/node_modules/next/dist/client/index.js:641:18
Root@webpack-internal:///../.yarn/$$virtual/next-virtual-6f4174843d/0/cache/next-npm-10.0.0-82dc2f1372-c01b177cb2.zip/node_modules/next/dist/client/index.js:757:18

Looking forward for a fix in this issue 😃

Your provider is setting the state in the render. If you put it in a useEffect then the problem will go away 😃

It seems it’s not a problem with the library but the understanding of how it’s used in the react lifecycle.

If you had a useState for example, you wouldn’t update that directly in the render method, you’d do it inside an effect.

I found this error when:

  • I had a setState in parent component (e.g const [state, setState] = useState({}))
  • I passed this setState to a child component and used it in a onClick={setState(“value”)}
  • I solved this problem adding the arrow function in the onClick call

this worked for me. @DaniCastel any reason an arrow function had to be used? React newbie here

This happen because my component use useRecoilValue(selector)

I found this error when:

  • I had a setState in parent component (e.g const [state, setState] = useState({}))
  • I passed this setState to a child component and used it in a onClick={setState(“value”)}
  • I solved this problem adding the arrow function in the onClick call

I have the same issue when an atom has a Promise as a default value.

Yep, same issue here… it looks like the warning is only shown using selectors, pure atoms seems to be working without a warning.

Looking forward to a fix, thanks for all the hard work!

@davidmccabe - that would be great - pls post any updates when you have an ETA. Thanks again.

If I get this warning once and everything else is working as expected, should I be concerned? Does it indicate a memory leak or something else that will affect the performance of the app?

I was in your shoes last year. I was getting this warning, and everything else was ok. My problem was when I started coding unit tests. Them, this warning was preventing my recoil state from being updated on test environments.

So, this is still problem.

@mattmogford-alcumus I completely agree that Recoil is life changing 😄 However it’s not fair to say there’s been no updates. There are quite frequent updates to master and I believe the team will eventually get to fix this issue 😄 There are several related commits already

I just saw the comment from @davidmccabe talking about the re-work and subsequent fix. So that is great news! GitHub should have a way for Contributors to ‘pin’ an important comment such as that to the top, sometimes there is a lot of chaff to wade through to find the important information. I did mean updates as in a tagged version, but yes that was unfair to say with all the work on issues etc… aaand I’m adding to the chaff!

Also encountered this warning today, decided to step back to react@16.12.0 and react-dom@16.12.0 in the meantime. Thanks guys for your work, this library looks very promising!

I have the same issue

@markerikson That was my misunderstanding too, because they said at some point about considering the use of useMutableSource as one option.

https://github.com/facebookexperimental/Recoil/issues/5#issuecomment-628796942

In Recoil, state seems React based.

+1 Having the same issue right now using React 18.0.25 & Recoil 0.7.6.

I have the same issue. It is caused by setting pageTitle (displayed in explorer tab and history) in each component after routing using recoil. Fixed after centralizing the pageTitle logic in the root of component where pageTitle is rendered and get rid of the recoil setting action.

For me it was a stupid mistake (typo). I had a memoized function, which inside of its dependencies had a Recoil setter being invoked/called, instead of just passed in the deps array.

Problematic code:

const [, setHasUnsavedChanges] = useRecoilState(hasUnsavedChangesState);

const save = useCallback(
    () => {
        // ...
    },
    [setHasUnsavedChanges()] // <--- Here's what was wrong, function was invoked, which is wrong
);

Fixed code:

const [, setHasUnsavedChanges] = useRecoilState(hasUnsavedChangesState);

const save = useCallback(
    () => {
        // ...
    },
    [setHasUnsavedChanges] // <--- Here it is fixed
);

@syunto07ka I have the same problem, but when I run the “npm run prod” code, this error disappears. I only see this error in development mode.

Running in prod mode only hides the errors, it still happens. Have a look at my comment above and it should explain what needs to be done

@drarmstr @davidmccabe I can see that 0.13 is published on npm, but it’s not listed under releases page here, can we see a change log?

Yes, #581 is still landing

@mattmogford-alcumus I completely agree that Recoil is life changing 😄 However it’s not fair to say there’s been no updates. There are quite frequent updates to master and I believe the team will eventually get to fix this issue 😄 There are several related commits already

I think replaceState shouldn’t cause Batcher update when we call getRecoilValueAsLoadable, since getRecoilValueAsLoadable just get value from RecoilNode for computing derived value. I think the following code would work, But I am not for sure. @davidmccabe

const replaceState = (replacer, shouldNotNotifyBatcher) => {
    \\ other code
    if (shouldNotifyBatcher === true) return;
    nullthrows(notifyBatcherOfChange.current)();
};
function getRecoilValueAsLoadable<T>(
  store: Store,
  {key}: AbstractRecoilValue<T>,
): Loadable<T> {
  let result: Loadable<T>;
  // Save any state changes during read, such as validating atoms,
  // updated selector subscriptions/dependencies, &c.
  Tracing.trace('get RecoilValue', key, () =>
    store.replaceState(
      Tracing.wrap(state => {
        const [newState, loadable] = getNodeLoadable(store, state, key);
        result = loadable;
        return newState;
      }),
      true
    ),
  );
  return (result: any); // flowlint-line unclear-type:off
}