Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling undefined/null properties in components during first render

I'm learning react and it's great, but i've ran into an issue and i'm not sure what the best practice is to solve it.

I'm fetching data from an API in my componentDidMount(), then i'm setting some states with SetState().

Now the problem is that because the first render happens before my states have been set, im sending the initial state values into my components. Right now i'm setting them to empty arrays or empty Objects ({ type: Object, default: () => ({}) }). Then i'm using ternary operator to check the .length or if the property has a value.

Is this the best practice or is there some other way that i'm unaware of?

I would love to get some help with this, so that i do the basics correctly right from the start.

Thanks!

like image 592
Robert Danielsson Avatar asked Mar 26 '19 14:03

Robert Danielsson


3 Answers

I think the best practice is to tell the user that your data is still loading, then populate the fields with the real data. This approach has been advocated in various blog-posts. Robin Wieruch has a great write up on how to fetch data, with a specific example on how to handle loading data and errors and I will go through his example here. This approach is generally done in two parts.

  1. Create an isLoading variable. This is a bolean. We initially set it to false, because nothing is loading, then set it to true when we try to fetch the data, and then back to false once the data is loaded.
  2. We have to tell React what to render given the two isLoading states.

1. Setting the isLoading variable

Since you did not provide any code, I'll just follow Wieruch's example.

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      dataFromApi: null,
    };
  }

  componentDidMount() {
    fetch('https://api.mydomain.com')
      .then(response => response.json())
      .then(data => this.setState({ dataFromApi: data.dataFromApi }));
  }

  ...
}

export default App;

Here we are using the browser's native fetch() api to get the data when the component mounts via the use of componentDidMount(). This should be quite similar to what you are doing now. Given that the fetch() method is asynchronous, the rest of the page will render and the state will be up dated once the data is received.

In order to tell the user that we are waiting for data to load, we simply add isLoading to our state. so the state becomes:

this.state = {
  dataFromApi: null,
  isLoading: false,
};

The state for isLoading is initially false because we haven't called fetch() yet. Right before we call fetch() inside componentDidMount() we set the state of isLoading to true, as such:

this.setState({ isLoading: true });

We then need to add a then() method to our fetch() Promise to set the state of isLoading to false, once the data has finished loading.

.then(data => this.setState({ dataFromAPi: data.dataFromApi, isLoading: false }));

The final code looks like this:

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      dataFromApi: [],
      isLoading: false,
    };
  }

  componentDidMount() {
    this.setState({ isLoading: true });

    fetch('https://api.mydomain.com')
      .then(response => response.json())
      .then(data => this.setState({ dataFromApi: data.dataFromApi, isLoading: false }));
  }

  ...
}

export default App;

2. Conditional Rendering

React allows for conditional rendering. We can use a simple if statement in our render() method to render the component based on the state of isLoading.

class App extends Component {
  ...

  render() {
    const { hits, isLoading } = this.state;

    if (isLoading) {
      return <p>Loading ...</p>;
    }

    return (
      <ul>
        {dataFromApi.map(data =>
          <li key={data.objectID}>
            <a href={data.url}>{data.title}</a>
          </li>
        )}
      </ul>
    );
  }
}

Hope this helps.

like image 71
pooya72 Avatar answered Oct 17 '22 18:10

pooya72


It Depends. suppose you are fetching books data from server. here is how to do that.

state = {
  books: null,
}

if, your backend api is correctly setup. You will get either empty array for no books or array with some length

componentDidMount(){
    getBooksFromServer().then(res => {
     this.setState({
     books: res.data
   })
  })
 }

Now In Your render method

   render() {
         const { books } = this.state;
         let renderData;
         if(!books) {
          renderData = <Spinner />
      } else
        if(books.length === 0) {
        renderData = <EmptyScreen />
       }
      else {
     renderData = <Books data = { books } />
   }
   
   return renderData;
   }  

If you are using offline data persistence In that case initially you won't have empty array.So This way of handling won't work. To show the spinner you have to keep a variable loader in state. and set it true before calling api and make it false when promise resolves or rejects.

finally read upon to state.

   const {loader} = this.state;
   if(loader) {
   renderData = <Spinner />
}
like image 21
shubham choudhary Avatar answered Oct 17 '22 18:10

shubham choudhary


I set initial state in constructor. You can of course set initial state of component as static value - empty array or object. I think better way is to set it using props. Therefore you can use you component like so <App items={[1,2,3]} /> or <App /> (which takes value of items from defaultProps object because you not pass it as prop).

Example:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

    class App extends Component {
        constructor(props) {
            super(props);
            this.state = {
                items: [], // or items: {...props.items} 
            };



        }
        async componentDidMount() {
            const res = await this.props.getItems();
            this.setState({items: res.data.items})
        }
        render() {
               return <div></div>
           }
};

App.defaultProps = {
items: []
}
like image 41
miuosh Avatar answered Oct 17 '22 18:10

miuosh