Recoil icon indicating copy to clipboard operation
Recoil copied to clipboard

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

Open syunto07ka opened this issue 4 years ago • 85 comments

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;

syunto07ka avatar May 14 '20 20:05 syunto07ka

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

ecreeth avatar May 14 '20 20:05 ecreeth

Thanks, ecreeth. As you say that, my code works, but warning is occured in my browser.

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

syunto07ka avatar May 14 '20 20:05 syunto07ka

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.)

dai-shi avatar May 15 '20 12:05 dai-shi

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

The error is present in both the codesandbox links

slapner avatar May 15 '20 13:05 slapner

it's my installed dependences in pakcage.json. so yeah, "16.13.1" is correct.

"dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1",
    "recoil": "0.0.7"
  },

syunto07ka avatar May 15 '20 13:05 syunto07ka

Thanks for reporting, I am investigating this.

davidmccabe avatar May 15 '20 16:05 davidmccabe

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...

somonek avatar May 18 '20 13:05 somonek

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...

I noticed also that setters stop working when hot reloading is set ... so having this related as well sounds like something worth exploring

djwglpuppy avatar May 18 '20 16:05 djwglpuppy

Yes, we have a known issue with hot module loading at the moment.

drarmstr avatar May 18 '20 16:05 drarmstr

Having the same issue. Thanks for reporting @syunto07ka and thanks for investigating @davidmccabe.

vandercloak avatar May 19 '20 00:05 vandercloak

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

lindskogen avatar May 19 '20 16:05 lindskogen

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).

incepter avatar May 19 '20 17:05 incepter

What if Batcher itself was rendered whenever a selector was rendered. eg, treat it as if it depends on all selectors. Then it would automatically queue its useEffect, and you would not need notify the Batcher of a change in replaceState, Batcher could test for the change when running it's useEffect. I think this also solves the "double render" whenever a selector changes dependencies.

jjrdn avatar May 19 '20 18:05 jjrdn

I see now that replaceState is called under other conditions that are not part of a render.

This particular issue occurs because:

  1. selectors are run during render
  2. selectors update their dependencies when they run
  3. updating dependencies calls replaceState
  4. replaceState notifies the Batcher through use of setState on the Batcher, which happens during the render of the component that uses the selector

jjrdn avatar May 19 '20 18:05 jjrdn

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 avatar May 20 '20 02:05 davidmccabe

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

atanasster avatar May 20 '20 02:05 atanasster

I am getting the same error, In my case this error pops up if I use the selector variable as a dependency in my component's useEffect For eg: in recoil:

// get decoded URL of meetingId
export const getDecodedMeetingId = selector ({
    key: 'decodedMeetingIdState',
    get: ({get}: any) => {
        const meetingId = get(meetingIdState);
        return decodeURI(meetingId);
    }
});

And then my component useEffect

const meetingStatus = useRecoilValue(getDecodedMeetingId);
// side-effects
    useEffect(()=>{
        if (meetingStatus) {
            console.log('Begin Timer');
            startTimer();
        } else {
            setTimer(prev => prev);
        }
    },[meetingStatus]);

Hopefully this helps. If I am doing something wrong, please let me know.

avj2352 avatar May 20 '20 05:05 avj2352

@davidmccabe I'm very curious - can you give any technical details on what you're doing and how it will improve CM compat? My general understanding was that any non-React-based state management approach will have issues with CM, because it can't tie in to React's ability to reorder state updates based on render priorities.

markerikson avatar May 25 '20 13:05 markerikson

@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.

dai-shi avatar May 25 '20 13:05 dai-shi

Hi, any ETA on this fix ? https://github.com/facebookexperimental/Recoil/issues/12#issuecomment-631201906 This is the problem, I am facing when using recoil... https://github.com/facebookexperimental/Recoil/issues/12#issuecomment-631238293

avj2352 avatar May 28 '20 16:05 avj2352

Confirming Recoil breaking hmr/fast refresh.

jaredpalmer avatar Jun 02 '20 15:06 jaredpalmer

Hi, i found this comment from react-final-form where they had a similar issue and solved it: https://github.com/final-form/react-final-form/issues/751#issuecomment-606212893

Hope this helps

farukg avatar Jun 10 '20 00:06 farukg

I have the same issue

mphung97 avatar Jun 18 '20 13:06 mphung97

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
}

markstock7 avatar Jun 20 '20 08:06 markstock7

Same issue.

yishuxin avatar Jun 23 '20 18:06 yishuxin

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!!

n8sabes avatar Jun 24 '20 06:06 n8sabes

I got his warning because I used a selector get another async selector get. Some example code

export const asyncSelector = selector>({
    key: 'AsyncSelector ',
    get: async ({ get }) => {
        const response = await fakeData();
        const data = await response.json();
        await new Promise((resolve) => {
            setTimeout(() => {
                resolve();
            }, 200);
        });
      return data
    },
});

export const getAsyncSelector= selectorFamily({
    key: 'GetAsyncSelector',
    get: (params) => ({ get }) => {
       return get(asyncSelector ).slice(params);
    },
});

Tatametheus avatar Jun 26 '20 14:06 Tatametheus

Also encountered this warning today, decided to step back to [email protected] and [email protected] in the meantime. Thanks guys for your work, this library looks very promising!

nikitaeverywhere avatar Jun 28 '20 08:06 nikitaeverywhere

Any update for this issue :( i try to add this to new project instead of redux ...

khanh19934 avatar Jun 28 '20 13:06 khanh19934

// store.js
export const todoListTypeState = atom({
  key: "todoListTypeState",
  default: "snack",
});

export const todoListState = atom({
  key: "todoListState",
  default: [
    { type: "snack", content: "cake" },
    { type: "drink", content: "water" },
  ],
});

export const todoListSelector = selector({
  key: "todoListSelector",
  get: ({ get }) => {
    const type = get(todoListTypeState);
    const todoList = get(todoListState);
    return todoList.filter((item) => item.type === type);
  },
  set: ({ set }, newValue) => {
    set(todoListState, newValue);
  },
});

// Todo.js
const todoList = useRecoilValue(todoListState); //  :} good
const [typedTodoList, setTodoList] = useRecoilState(todoListSelector); //  :{ warning

waitting...

Holybasil avatar Jul 02 '20 13:07 Holybasil