Search code examples
javascriptreactjsreduxfluxarrow-functions

"setState(…): Can only update a mounted or mounting component" inside componentDidMount


Related question here but I am not sure how to adapt the solution to this problem.

I am trying to create a reusable component for a landing page with tabs. Each tab is a child of the reusable component and has its own store defined as a prop:

<LandingPage>
    <LandingPage.Tab store={store1}/>
    <LandingPage.Tab store={store2}/>
    ...
    <LandingPage.Tab store={storeN}/>
</LandingPage>

I'd like to fetch data from every tab's store when the parent component mounts to allow quick switching between tabs. Inside the componentDidMount function, I iterate over each child and assign the onChange callback for the child's store to an anonymous arrow function:

var LandingPage = React.createClass({
    getInitialState: function () {
        return {
            data: [] /* each index will be an array of data for a different tab */
        };
    },
    componentDidMount: function () {
        var self = this;
        React.Children.forEach(this.props.children, function (child, index) {
            child.props.store.onChange(() => {
                self.setDataAtIndex(index, child.props.store.getData());
            });
        });
    },
    setDataAtIndex: function (index, newData) {
        var data = this.state.data.slice();
        data[index] = newData;
        this.setState({
            data: data
        });
    },
    ...
});

However, when the page first loads, I get a warning message from React:

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the LandingPage component.

I am confused because I thought I could assume the component is mounted if I am inside the componentDidMount function. This warning message goes away when I refresh the page.

Can someone explain this behavior and tell me how to correctly structure code to eliminate the warning message?


Solution

  • This function...

    () => { // this calls setState
        self.setDataAtIndex(index, child.props.store.getData());
    }
    

    Will be called every time a tabs store changes whether the LandingPage component is mounted or not. That is a problem. You need to tell the stores to stop calling this function when LandingPage unmounts. Without modifying the store, you could just override the change listener with a no-op, like this...

    componentWillUnmount: function () {
        var self = this;
        React.Children.forEach(this.props.children, function (child, index) {
            child.props.store.onChange(() => {});
        });
    }
    

    Now, when the component isn't mounted, () => {} should be called instead, which doesn't call setState and is therefore harmless