Search code examples
reactjsreduxreact-redux

Re-render React component when prop changes


I'm trying to separate a presentational component from a container component. I have a SitesTable and a SitesTableContainer. The container is responsible for triggering redux actions to fetch the appropriate sites based on the current user.

The problem is the current user is fetched asynchronously, after the container component gets rendered initially. This means that the container component doesn't know that it needs to re-execute the code in its componentDidMount function which would update the data to send to the SitesTable. I think I need to re-render the container component when one of its props(user) changes. How do I do this correctly?

class SitesTableContainer extends React.Component {
    static get propTypes() {
      return {
        sites: React.PropTypes.object,
        user: React.PropTypes.object,
        isManager: React.PropTypes.boolean
      }
     }

    componentDidMount() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }

    render() {
      return <SitesTable sites={this.props.sites}/>
    }
}

function mapStateToProps(state) {
  const user = userUtils.getCurrentUser(state)

  return {
    sites: state.get('sites'),
    user,
    isManager: userUtils.isManager(user)
  }
}

export default connect(mapStateToProps)(SitesTableContainer);

Solution

  • You have to add a condition in your componentDidUpdate method.

    The example is using fast-deep-equal to compare the objects.

    import equal from 'fast-deep-equal'
    
    ...
    
    constructor(){
      this.updateUser = this.updateUser.bind(this);
    }  
    
    componentDidMount() {
      this.updateUser();
    }
    
    componentDidUpdate(prevProps) {
      if(!equal(this.props.user, prevProps.user)) // Check if it's a new user, you can also use some unique property, like the ID  (this.props.user.id !== prevProps.user.id)
      {
        this.updateUser();
      }
    } 
    
    updateUser() {
      if (this.props.isManager) {
        this.props.dispatch(actions.fetchAllSites())
      } else {
        const currentUserId = this.props.user.get('id')
        this.props.dispatch(actions.fetchUsersSites(currentUserId))
      }  
    }
    

    Using Hooks (React 16.8.0+)

    import React, { useEffect } from 'react';
    
    const SitesTableContainer = ({
      user,
      isManager,
      dispatch,
      sites,
    }) => {
      useEffect(() => {
        if(isManager) {
          dispatch(actions.fetchAllSites())
        } else {
          const currentUserId = user.get('id')
          dispatch(actions.fetchUsersSites(currentUserId))
        }
      }, [user]); 
    
      return (
        return <SitesTable sites={sites}/>
      )
    
    }
    

    If the prop you are comparing is an object or an array, you should use useDeepCompareEffect instead of useEffect.