M Xiao
M Xiao

Reputation: 663

React Lifecycle ( componentDidMount ) with Redux

I have been a bit confused about the React Lifecycle, specifically how componentDidMount interacts with redux.

Here's the scenario: I have a component connected to the redux store, and I don't know if componentDidMount is supposed to be called again whenever the redux store is updated and the props in the connected component change. Right now, one component does remount every time the props change. HOWEVER, I have another separate connected component that doesn't remount when its props change. So now I'm lost because of this inconsistent behavior.

I looked around, but some of the answers like Why is componentDidMount not being called when I re-render? and ComponentDidMount called multiple times are confusing me.

Here's an example of the code that isn't updating properly when the props change

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from "redux";
import PropTypes from 'prop-types';


/* material UI */
import Collapse from '@material-ui/core/Collapse';
import Divider from '@material-ui/core/Divider';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import Tooltip from '@material-ui/core/Tooltip';

/* Icons */
import {Copy,FolderMinus,FolderPlus} from 'react-feather';

/* Our Components */
import { history } from '../../redux/store/index';
import { getClusters, setCluster, toggleClusters } from '../../redux/actions/ClusterActions';


class SideClustersList extends Component{
  constructor(props){
    super(props);
  
  }
  componentDidMount(){
    /* get a list of clusters based on the apiurl */
    console.log("props get clusters")
    console.log(this.props.base_url)
    this.props.getClusters(this.props.base_url);
  }
  setCurrentCluster(cluster) {
    this.props.setCluster(this.props.base_url, cluster)
    // Switch tabs to display selected cluster
    history.push("/clusters")
  }
  render(){
    const { classes,clusters, open } = this.props;
    return (
      <React.Fragment>
        <ListItem button className={classes.nested} onClick={this.handleClick}>
            <Tooltip id="tooltip-bottom" title="Clusters" placement="top">
          <ListItemIcon >
            {open ? <FolderMinus/>:<FolderPlus/>}
          </ListItemIcon>
            </Tooltip>
          <ListItemText primary="Clusters" /></ListItem>
        <Collapse in={open} timeout="auto" unmountOnExit>
          <Divider />
        <List component="div" disablePadding>
        {clusters ? (
          clusters.map(cls =>(
          <ListItem button key={cls.clusterName} component='a'
            /*href={'/clusters/'+this.props.accounts[0]['account_name']+'/'+cls.clusterName}*/
            onClick={() => this.setCurrentCluster(cls.clusterName)}>
            <Tooltip id="tooltip-top" title={cls.clusterName} placement="top">
            <ListItemIcon>
              <Copy />
            </ListItemIcon>
            </Tooltip>
            <ListItemText primary={cls.clusterName} />
      </ListItem>
    ))): null}
      </List>
      </Collapse>
      </React.Fragment>
    )
  }
  handleClick = () => {
    this.props.toggleClusters(!this.props.open)
  };

}
SideClustersList.propTypes = {
  getClusters: PropTypes.func.isRequired
};

const mapStateToProps = state => ({
  account_name: state.accounts.item.account_name,
  base_url: state.accounts.item.apiurl,
  cluster: state.clusters.item,
  clusters: state.clusters.items,
  open: state.clusters.open,
});

function mapDispatchToProps(dispatch) {
  return bindActionCreators({getClusters, setCluster, toggleClusters}, dispatch)
}

export default connect(
  mapStateToProps, mapDispatchToProps
)(SideClustersList);

Upvotes: 0

Views: 1323

Answers (2)

Zachary Melancon
Zachary Melancon

Reputation: 99

React uses Reconciliation to establish when a component should unmount then remount. React does diff checking to determine when to update a component and all its children. It will not remount any components when nothing has changed at the parent level, and therefore componentDidMount will not be called again if it was only some aspect of the component's children that changed. So if you provide a different possibility of returning a new component group, for example if you're returning a Activity Indicator when state is being fetched asynchronously, then each time the different component group is remounted you'll get the componentDidMount call.

Upvotes: 1

Łukasz Wojciechowski
Łukasz Wojciechowski

Reputation: 936

When Redux gets the new data, it triggers props change through connect down to all connected components. There are some possibilities for how the component will behave according to the store changes:

  1. When the connection to the store does not affect parents of the component, it will launch regular component lifecycle:

    • static getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()

    When this happens the component will not call componentDidMount as you can see.

  2. When the parents of the child are affected, there is a possibility that the component will be removed from the dom and entered again. When is it possible? For example when the parent component decides that provided props are new and it should rerender all its children. When this happens you enter the path where the component is unmounted from the dom and componentWillUnmount() is called and then mounted again with the regular lifecycle methods:

    • constructor()
    • static getDerivedStateFromProps()
    • render()
    • componentDidMount()

You can read more about reconciliation here: https://reactjs.org/docs/reconciliation.html

Upvotes: 3

Related Questions