1

Currently I get my data from an API in a JSON-format when running my saga. The fetching process begins, when the component did mount. That means the component renders two times.

Now, when the data is available as props. I can use it in order to render it.

My approach to this is like following, I have got a:

  1. Constructor with the initial state
  2. I fetch data in "componentDidMount"
  3. I got a function that takes the JSON properties from props and puts it into new variables
  4. I run this function in my render() function, when the props contain the fetched data

The Problem in this approach: Once the component runs the function where the data becomes "structured", the render-function loops and then after some time, the values of the properties get displayed with a warning message in the console.

My Questions:

  1. How to prevent the looping when render() runs once?
  2. How can I design this, so that particular properties of the fetched object merge into a new object and how to

I hope I described the most important things about my issue. Here is the code:

class Dashboard extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        deviceInfo: {
            name: "Initial Name",
            batLevel: "78%",
        }
    }
}

componentDidMount() {
    this.props.requestApiData();
}

updateDeviceInfoWithState (){
    const devices = (this.props.data.data);

    if(devices){
        const newDeviceInfo = this.state.deviceInfo;
        newDeviceInfo.name = devices[0].shadow.desired.payload.refAppData.name;
        newDeviceInfo.batLevel = devices[0].shadow.reported.payload.refAppData.batteryState.level;
        this.setState({
            deviceInfo: newDeviceInfo,
        });
    }
}

render() {
    this.updateDeviceInfoWithState()

    return (
        <div className='container'>
              <p> {this.state.deviceInfo.name} </p>
              <p> {this.state.deviceInfo.batLevel} </p>
        </div>
    )
}...
Aleks
  • 101
  • 1
  • 12

2 Answers2

1

Updating the state in the render method is not a good practice, since it might cause an infinite loop.

In your case state is redundant, since you only take the data from props, or replace it with defaults. Instead of using the state return the name and batLevel in the updateDeviceInfoWithState method, and use it in the render method.

Example (not tested):

class Dashboard extends React.Component {
  componentDidMount() {
      this.props.requestApiData();
  }

  updateDeviceInfoWithState (){
      const devices = this.props.data.data;

      if(devices){
        const device = devices[0].shadow;

        return {
          name: device.desired.payload.refAppData.name,
          batLevel: device.reported.payload.refAppData.batteryState.level
        };
      }

      return  {
        name: "Initial Name",
        batLevel: "78%",
      };
  }

  render() {
      const { name, batLevel } = this.updateDeviceInfoWithState();

      return (
          <div className='container'>
                <p> {name} </p>
                <p> {batLevel} </p>
          </div>
      );
}...

Note 1: If you want to decouple your component from the state, it's better to enforce simple properties as input for the data. For example, this component needs as properties the name and batLevel. It doesn't need to be aware of the array of devices, shadow, payload, etc... You can prepare the data when you receive it in the saga, or use a redux selector in mapStateToProps.

Note 2: If you really need the data in your state, you can use the getDerivedStateFromProps life-cycle method (React 16.3), or update the state in the componentWillReceiveProps if you use an older version.

Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • This is working! Your second edit was needed. I had to return {name} and {batLevel} instead of {this.state.deviceInfo.name}. Thank you! – Aleks Jul 21 '18 at 22:02
  • You're welcome :) It suddenly dawned on me that I forgot to alter the actual usage of the state in the render method. – Ori Drori Jul 21 '18 at 22:05
  • How is it possible to put these two properties in a new object? For instance I want to use a table to display the data of "devices", therefore I need an object that has to be passed to the table. – Aleks Jul 21 '18 at 22:06
  • Return whatever you want from the `updateDeviceInfoWithState` method (an object with the two properties), or just iterate the `devices` array in render, and create the elements directly. You can make your life easier by formatting the data in the saga, or in a redux selector, so you'll just render the data in the component. – Ori Drori Jul 21 '18 at 22:09
0

For this case you can use ComponentWillRecieveProps method like this

componentWillRecieveProps(nextProps) {
// Condition as per ur requirement.
If(this.props.data != nextProps.data) {
    this.updateDeviceInfoWithState(nextProps)
}

}

This method will only run whenever ur component props are changed.

Gurpreet Singh
  • 209
  • 1
  • 9
  • Note `componentWillRecieveProps` is being deprecated. https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops – wdm Jul 21 '18 at 22:02