3

I think I'm missing a concept here about React and Redux. I'm trying to work with objects stored in redux, and I'm having trouble.

REDUX: I have an action fetchItems, that gets all items from the database. This action works successfully.

REACT: I have a container, UserProfile, that calls fetchItems in componentDidMount.

class UserProfile extends Component {

  componentWillMount() {
    console.log('------------ USER PROFILE -------------------');
  }

  componentDidMount() {
    console.log('[ComponentDidMount]: Items: ', this.props.items);
    this.props.fetchItems();
  }

  render() {
    let profile = null;
    console.log('[Render]: Items: ', this.props.items);

    return <Auxillary>{profile}</Auxillary>;
  }
}

const mapStateToProps = state => {
  return {
    items: state.items.items
  };
};

const mapDispatchToProps = dispatch => {
  return {
    fetchItems: () => dispatch(actions.fetchItems())
  };
};

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

The problem I'm seeing is that this.props.items is always null (even though fetchItems is successful). The only way I can detect that items were stored in redux store is if I use componentWillRecieveProps(nextProps). Here, I successfully see the items in nextProps. I feel like using componentWillReceiveProps might be too "messy" though. I guess what I'm asking is, what is the standard way of dealing with updates to redux states in react?

Aseel

Asool
  • 13,031
  • 7
  • 35
  • 49
  • 2
    The life cycle of a component is as follows: `constructor() componentWillMount() render() componentDidMount()` so anything executed in `componentDidMount()` will get executer after `render()` – Yasir Feb 11 '18 at 17:04

4 Answers4

4

The cycle will be :

  • constructor()
  • componentWillMount() (will be soon deprecated by the way : https://medium.com/@baphemot/whats-new-in-react-16-3-d2c9b7b6193b)
  • render() => first render (this.props.items, coming from mapStateToProps will be undefined)
  • componentDidMount() => launching fetchItems() => changing redux state => changing the this.props.items => launching the second render() where this.props.items will be set.

So :

  • you should have two console.log('[Render]: Items: ', this.props.items);
  • you should deal with a "loading" state when the this.props.items is null

If the second console.log is still null, Try to add log in your reducer, in the mapStateToProps, ... perhaps it's not state.items.items ...

2

In react, we have something called state. if the state of a component is changed the component will re-render. Having said that we can use this.setState() inside componentWillRecieveProps to update the state which in turn will rerender the component. So your code will look like this which is the standard way to handle Redux level state changes in react.

class UserProfile extends Component {

    constructor(props) {
        super(props);
        this.state = {
            items: props.items
        }
    }
    componentWillMount() {
      console.log('------------ USER PROFILE -------------------');
    }

    componentWillRecieveProps({ items }) {
        this.setState({ items });
    }

    componentDidMount() {
      console.log('[ComponentDidMount]: Items: ', this.state.items);
      this.props.fetchItems();
    }

    render() {
      let profile = null;
      console.log('[Render]: Items: ', this.state.items);

      return <Auxillary>{profile}</Auxillary>;
    }
  }

  const mapStateToProps = state => {
    return {
      items: state.items.items
    };
  };

  const mapDispatchToProps = dispatch => {
    return {
      fetchItems: () => dispatch(actions.fetchItems())
    };
  };

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

P.S Just making the API call inside componentWillMount will not help either as API call is async and can take up some time to resolve and till then react will finish rendering the component. so you'll still have to use componentWillRecieveProps

priyansh gupta
  • 293
  • 1
  • 12
1

Standard practice is to call this.props.fetchItems() in your constructor or componentWillMount().

componentDidMount is called after render which is why your items do not render - they do not exist until after the initial render.

luetkemj
  • 171
  • 7
1

There are certain ways you can resolve this. The very first time when render() gets called it was subscribed to the initial props/state that was initialise in redux store through redux connect method. In your case items was null. Always initialise your redux store with some meaningful data.

In your case if items will be array you can initialise with empty array. When you dispatch action your store will get updated and the component which was subscribed to items will be re rendered and in this way you donot have to use setState inside componentWillReceiveProps and you can avoid using it.

You need to handle certain cases in render like if array is empty and data is still loading then show some kind of loader and once data is fetched then display it.

Deepak
  • 293
  • 3
  • 9