Search code examples
reactjsfluxreactjs-flux

Child component removed by parent still gets store change event


tl;dr: same issue as described by mridgway here: isMounted is not enough to know if you can setState

I have a components hierarchy like this:

PhasesList
- Phase
  - PhaseHeader
- Phase
  - PhaseHeader

And a PhaseStore, which maintains a list of phases, along with their details.

In all three components, I listen for PhaseStore changes and update state (rerender) based on changes. For example if user changes a phase name in PhaseHeader, I trigger action, store updates and emits change event. That change event is propagated to PhaseList, Phase and PhaseHeader which are rendered with actual name value.

There is an problematic case where I can remove a phase. This action is handled in PhaseStore, where the phase is removed from list and change event is emitted (as in other cases). This event is handled by all components, from top to bottom (since all listen to store change).

So, in PhasesList, new set of phases is rendered, without the one removed. However, that removed phase component still receives the change event, as well as the PhaseHeader does.

In these two components, in phase change handler I use setState. The message I'm getting is this:

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.

I found out people used isMounted to check that component is still in DOM. This method is now deprecated.

How could I solve this in proper, flux way? What's the issue with design of my components and their intercommunication?


Solution

  • Normally you should just use React Redux that generates container components. It is already heavily optimized, will work faster than manual code using setState(), and specifically has a protection for the case you mentioned.

    Otherwise you can do this:

    componentDidMount() {
      this.unsubscribe = store.subscribe(this.handleChange.bind(this));
    }
    
    componentWillUnmount() {
      this.unsubscribe();
      this.unsubscribe = null;
    }
    
    handleChange() {
      if (!this.unsubscribe) {
        return;
      }
    
      this.setState(/* ... */);
    }