68

I am using Redux with Class Components in React. Having the below two states in Redux store.

{ spinner: false, refresh: false }

In Parent Components, I have a dispatch function to change this states.

class App extends React.Component {
  reloadHandler = () => {
    console.log("[App] reloadComponent");

    this.props.onShowSpinner();
    this.props.onRefresh();
  };

  render() {
    return <Child reloadApp={this.reloadHandler} />;
  }
}

In Child Component, I am trying to reload the parent component like below.

class Child extends React.Component {
  static getDerivedStateFromProps(props, state) {
    if (somecondition) {
      // doing some redux store update
      props.reloadApp();
    }
  }

  render() {
    return <button />;
  }
}

I am getting error as below.

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

How to remove this warning? What I am doing wrong here?

Smith Dwayne
  • 2,675
  • 8
  • 46
  • 75

16 Answers16

59

For me I was dispatching to my redux store in a React Hook. I had to dispatch in a useEffect to properly sync with the React render cycle:

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

  const formattedData = useMemo(() => {
    // DISPATCHING HERE CAUSED THE WARNING
  }, [data])

  // DISPATCHING HERE CAUSED THE WARNING TOO

  // Note: Dispatching to the store has to be done in a useEffect so that React
  // can sync the update with the render cycle otherwise it causes the message:
  // `Warning: Cannot update a component from inside the function body of a different component.`
  useEffect(() => {
    orderbookStore.dispatch(setOrderbookData(formattedData))
  }, [formattedData])

  return { data: formattedData, error, loading }
}
Dominic
  • 62,658
  • 20
  • 139
  • 163
  • 1
    Legend. For me, I was dispatching to the Redux store without using a hook at all, and I'd still get this warning, but weirdly enough (as is the case with all things React), only with some dispatches, not all, even though they all update the store causing a re-render. I'd say it's a bug introduced in v16.13.0 with false reporting of the warning, as mentioned here: https://github.com/facebook/react/issues/18178#issuecomment-601425520 I'm certainly not updating one component from another, as the warning suggests, only the same component. In any case, `useEffect` made the warning go away. – Ash Jun 19 '20 at 13:53
  • This was it for me too. I removed my dispatch events one by one until I found the offending fn. Moved it to componentDidMount (or useEffect) and it worked! Since I was loading the component conditionally this was an issue. Alternatively you can be ratchet and put the flag that conditionally loads the component (such as the set method for your useState) in a `setTimeout` and it will work also... but you didn't hear it from me :x – Scott L Aug 10 '21 at 20:29
26

If your code calls a function in a parent component upon a condition being met like this:

const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
    const { data, loading, error } = useQuery(QUERY);

    if (data && data.users.length === 0) {
        return handleNoUsersLoaded();
    }

    return (
        <div>
            <p>Users are loaded.</p>
        </div>
    );
};

Try wrapping the condition in a useEffect:

const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
    const { data, loading, error } = useQuery(QUERY);

    useEffect(() => {
        if (data && data.users.length === 0) {
            return handleNoUsersLoaded();
        }
    }, [data, handleNoUsersLoaded]);

    return (
        <div>
            <p>Users are loaded.</p>
        </div>
    );
};
radihuq
  • 999
  • 10
  • 13
  • Wrapping the condition in a useEffect worked for me. I was getting the error with this code after fetching data: `if (data) dispatch(actionFunction({ data });` `if (!data) return ;` and this fixed it: `useEffect(() => {` `if (data) dispatch(actionFunction({ data });` `}, [data, dispatch]);` `if (!data) return ;` – beeftosino Dec 08 '20 at 08:09
  • Thank you for this, I had code that was responding to a change in props but I had forgotten to put it inside a `useEffect` hook. Appreciate your help! – nhuesmann Mar 24 '21 at 20:31
21

It seems that you have latest build of React@16.13.x. You can find more details about it here. It is specified that you should not setState of another component from other component.

from the docs:

It is supported to call setState during render, but only for the same component. If you call setState during a render on a different component, you will now see a warning:
Warning: Cannot update a component from inside the function body of a different component.
This warning will help you find application bugs caused by unintentional state changes. 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.


Coming to the actual question.

I think there is no need of getDerivedStateFromProps in the child component body. If you want to trigger the bound event. Then you can call it via the onClick of the Child component as i can see it is a <button/>.

class Child extends React.Component {
  constructor(props){
    super(props);
    this.updateState = this.updateState.bind(this);
  }
  updateState() { // call this onClick to trigger the update
    if (somecondition) {
      // doing some redux store update
      this.props.reloadApp();
    }
  }

  render() {
    return <button onClick={this.updateState} />;
  }
}
Jai
  • 74,255
  • 12
  • 74
  • 103
  • 1
    This answer helped me figured the problem. I hit the rare case. Wrapping it in useEffect silent the error. Thanks much. If anyone's still unsure, feel free to reach out. – f0rfun May 07 '20 at 02:39
  • 2
    @f0rfun you can post it here. That would surely help others as this is something new. – Jai May 07 '20 at 08:04
  • In a way this fixed my issue (which wasn't directly even related, but still). With morning's cotton eyes used `onPress={navigation.navigate(...)}` instead of `onPress={() => navigation.navigate(...)}` – Scre Oct 05 '21 at 07:19
17

Same error but different scenario

tl;dr wrapping state update in setTimeout fixes it.

This scenarios was causing the issue which IMO is a valid use case.

const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
  setSomeState(someNewValue);
}).current;
return (
  <SomeComponent onSomeUpdate={doUpdate} />
);

fix

const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
  setTimeout(() => {
   setSomeState(someNewValue);
  }, 0);
}).current;
return (
  <SomeComponent onSomeUpdate={doUpdate} />
);

King Friday
  • 25,132
  • 12
  • 90
  • 84
13

In my case I had missed the arrow function ()=>{}

Instead of onDismiss={()=>{/*do something*/}}

I had it as onDismiss={/*do something*/}

Community
  • 1
  • 1
Anand Rockzz
  • 6,072
  • 5
  • 64
  • 71
7

I had same issue after upgrading react and react native, i just solved that issue by putting my props.navigation.setOptions to in useEffect. If someone is facing same problen that i had i just want to suggest him put your state changing or whatever inside useEffect

Muhammad Faisal
  • 315
  • 3
  • 14
4

Commented some lines of code, but this issue is solvable :) This warnings occur because you are synchronously calling reloadApp inside other class, defer the call to componentDidMount().

import React from "react";

export default class App extends React.Component {
  reloadHandler = () => {
    console.log("[App] reloadComponent");

    // this.props.onShowSpinner();
    // this.props.onRefresh();
  };

  render() {
    return <Child reloadApp={this.reloadHandler} />;
  }
}

class Child extends React.Component {
  static getDerivedStateFromProps(props, state) {
    // if (somecondition) {
    // doing some redux store update
    props.reloadApp();
    // }
  }

  componentDidMount(props) {
    if (props) {
      props.reloadApp();
    }
  }

  render() {
    return <h1>This is a child.</h1>;
  }
}
vsr
  • 1,025
  • 9
  • 17
  • I honestly can't believe this isn't the accepted answer, or even a highly voted one. This has the exact cause and the perfect solution for it. – Lazerbeak12345 Dec 04 '20 at 21:23
2

I got this error using redux to hold swiperIndex with react-native-swiper

Fixed it by putting changeSwiperIndex into a timeout

Dazzle
  • 2,880
  • 3
  • 25
  • 52
  • I had the same issue and fixed it as you suggested... I'm wondering if we could not fix it in react-native-swiper directly... – Żabojad May 21 '21 at 14:56
1

I got the following for a react native project while calling navigation between screens.

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

I thought it was because I was using TouchableOpacity. This is not an issue of using Pressable, Button, or TouchableOpacity. When I got the error message my code for calling the ChatRoom screen from the home screen was the following:

    const HomeScreen = ({navigation}) => { 
    return (<View> <Button title = {'Chats'} onPress = { navigation.navigate('ChatRoom')} <View>) } 

The resulting behavior was that the code gave out that warning and I couldn't go back to the previous HomeScreen and reuse the button to navigate to the ChatRoom. The solution to that was doing the onPress in an inline anonymous function.

    onPress{ () => navigation.navigate('ChatRoom')}

instead of the previous

    onPress{ navigation.navigate('ChatRoom')}

so now as expected behavior, I can go from Home to ChatRoom and back again with a reusable button.

PS: 1st answer ever in StackOverflow. Still learning community etiquette. Let me know what I can improve in answering better. Thanx

Raihan Noman
  • 11
  • 1
  • 2
0

If you want to invoke some function passed as props automatically from child component then best place is componentDidMount lifecycle methods in case of class components or useEffect hooks in case of functional components as at this point component is fully created and also mounted.

Rajnikant
  • 2,176
  • 24
  • 23
0

I was running into this problem writing a filter component with a few text boxes that allows the user to limit the items in a list within another component. I was tracking my filtered items in Redux state. This solution is essentially that of @Rajnikant; with some sample code.

I received the warning because of following. Note the props.setFilteredItems in the render function.

import {setFilteredItems} from './myActions';
const myFilters = props => {
  const [nameFilter, setNameFilter] = useState('');
  const [cityFilter, setCityFilter] = useState('');

  const filterName = record => record.name.startsWith(nameFilter);
  const filterCity = record => record.city.startsWith(cityFilter);

  const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
  props.setFilteredItems(selectedRecords); //  <-- Danger! Updates Redux during a render!

  return <div>
    <input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
    <input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
  </div>
};

const mapStateToProps = state => ({
  records: state.stuff.items,
  filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);

When I ran this code with React 16.12.0, I received the warning listed in the topic of this thread in my browser console. Based on the stack trace, the offending line was my props.setFilteredItems invocation within the render function. So I simply enclosed the filter invocations and state change in a useEffect as below.

import {setFilteredItems} from './myActions';
const myFilters = props => {
  const [nameFilter, setNameFilter] = useState('');
  const [cityFilter, setCityFilter] = useState('');

  useEffect(() => {
    const filterName = record => record.name.startsWith(nameFilter);
    const filterCity = record => record.city.startsWith(cityFilter);

    const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
    props.setFilteredItems(selectedRecords); //  <-- OK now; effect runs outside of render.
  }, [nameFilter, cityFilter]);

  return <div>
    <input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
    <input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
  </div>
};

const mapStateToProps = state => ({
  records: state.stuff.items,
  filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);

When I first added the useEffect I blew the top off the stack since every invocation of useEffect caused state change. I had to add an array of skipping effects so that the effect only ran when the filter fields themselves changed.

pglezen
  • 961
  • 8
  • 18
  • I noticed a small problem with the solution I posted. When the records of the Redux store are updated asynchronously, the useEffect is updated, but with the props holding the old state as a closure from the previous render. The render does eventually happen, updating props. But the "skipping effect" prevents the effect from rerunning with these updated props. I fixed this by adding props.records.length to my skip list. – pglezen May 13 '20 at 15:03
0

I suggest looking at video below. As the warning in the OP's question suggests, there's a change detection issue with the parent (Parent) attempting to update one child's (Child 2) attribute prematurely as the result of another sibling child's (Child 1) callback to the parent. For me, Child 2 was prematurely/incorrectly calling the passed in Parent callback thus throwing the warning.

Note, this commuincation workflow is only an option. I personally prefer exchange and update of data between components via a shared Redux store. However, sometimes it's overkill. The video suggests a clean alternative where the children are 'dumb' and only converse via props mand callbacks.

Also note, If the callback is invoked on an Child 1 'event' like a button click it'll work since, by then, the children have been updated. No need for timeouts, useEffects, etc. UseState will suffice for this narrow scenario.

Here's the link (thanks Masoud):

https://www.youtube.com/watch?v=Qf68sssXPtM

MoMo
  • 1,836
  • 1
  • 21
  • 38
0

In react native, if you change the state yourself in the code using a hot-reload I found out I get this error, but using a button to change the state made the error go away.

However wrapping my useEffect content in a :

setTimeout(() => {
    //....
}, 0);

Worked even for hot-reloading but I don't want a stupid setTimeout for no reason so I removed it and found out changing it via code works just fine!

Steve Moretz
  • 2,758
  • 1
  • 17
  • 31
0

I was updating state in multiple child components simultaneously which was causing unexpected behavior. replacing useState with useRef hook worked for me.

GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
0

Try to use setTimeout,when I call props.showNotification without setTimeout, this error appear, maybe everything run inTime in life circle, UI cannot update.

const showNotifyTimeout = setTimeout(() => {
          this.props.showNotification();
          clearTimeout(showNotifyTimeout);
        }, 100);
  • Please add code and data as text ([using code formatting](//stackoverflow.com/editing-help#code)), not images. Images: A) don't allow us to copy-&-paste the code/errors/data for testing; B) don't permit searching based on the code/error/data contents; and [many more reasons](//meta.stackoverflow.com/a/285557). Images should only be used, in addition to text in code format, if having the image adds something significant that is not conveyed by just the text code/error/data. – Suraj Rao Oct 27 '21 at 05:12
0

Writing this answer for my future self googling for the answer: in react-native this can be resolved by using InteractionManager to wrap your setState() in your callback.

For example:

  const someCallbackFromAChild = (e) => {
    if (e.action === 'foo') {
      InteractionManager.runAfterInteractions(() => {
        setState('foobar');
      });
    }
  };

I've ran into this problem using a bottom sheet in react-native, where toggling a switch updates the view UNDERNEATH the sheet. InteractionManager.runAfterInteractions allows the animation of toggle switch to complete before attempting to render the view underneath it. As I understand it, this resolves "queuing" of the render phase, which is likely the cause of the warning.

jakeforaker
  • 1,622
  • 1
  • 20
  • 34