cpt_3n0x
cpt_3n0x

Reputation: 55

React - Loading data before render

I'm new to react and I have a question about a best practice that sees me make a mistake . I call an API to retrieve information and modify an array in the state once the response is returned by the API. In the "render" I have to retrieve the information from this array (when it is completed) or it sends me back an error because the array is empty when the render is initialized.

class MyClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      activeIndex: 0,
      items: []
    }
  }

  componentDidMount() {
    axios
      .get(`API_ADDRESS`, {
        headers: {
          Authorization: `Token XXX`,
        },
      })
      .then(function(response) {
        this.setState({
          items: response.results,
        })
      })
      .catch(error => {
        notification.warning({
          message: error.code,
          description: error.message,
        })
      })
  }

  changeDialog = (e, index) => {
    e.preventDefault()
    this.setState({
      activeIndex: index,
    })
  }

  render() {
    const { activeIndex, items } = this.state
    const {
      first_name: firstName,
      last_name: lastName,
      phone,
      email,
      address,
    } = items[activeIndex]

The error indicates :

TypeError: _items$activeInde is undefined

How can I solve this error related to data loading? (trying to keep the destrying elements method)

Thanks a lot Eliott

Upvotes: 2

Views: 9781

Answers (3)

acdcjunior
acdcjunior

Reputation: 135762

Two issues:

  • beware of this inside the Promise returned by axios. You use function(){} so the this inside is not the component's instance. Change it to an arrow function.
  • add a guard so you won't destructure undefined when activeIndex points to an item element that is not there (which happens in the initial loading before the axios fetches the data).

Fix:

// ... (code not shown remains unmodified)
componentDidMount() {
  axios
    .get(`API_ADDRESS`, {
      headers: {
        Authorization: `Token XXX`,
      },
    })
    .then(response => {                                             // changed this line
      this.setState({
        items: response.results,
      })
    })
// ... (code not shown remains unmodified)

render() {
  const { activeIndex, items } = this.state
  if (!items[activeIndex]) {                                        // added this line
    return <div>Hold tight while items are being fetched...</div>;  // added this line
  }                                                                 // added this line
  const {
    first_name: firstName,
// ... (code not shown remains unmodified)

Upvotes: 0

thortsx
thortsx

Reputation: 2280

Because API that you fetch from server is async. The first time render of Component, data that you setState in axios still not yet updated, it just updated when Component render the second time.

So you must check state in render Component like this to make sure that if activeIndex is defined then declare variable with items[activeIndex] :

activeIndex && const {
      first_name: firstName,
      last_name: lastName,
      phone,
      email,
      address,
} = items[activeIndex]

Upvotes: 1

spProgrammer
spProgrammer

Reputation: 50

just change your component like so:

  constructor(props) {
    super(props)
    this.state = {
      activeIndex: 0,
      items: [],
      isFetching: false
    }
  }

  componentDidMount() {
    // staring your fetching
    this.setState({isFetching: true});
    axios
      .get(`API_ADDRESS`, {
        headers: {
          Authorization: `Token XXX`,
        },
      })
      .then(function(response) {
        // finish fetching when your response is ready :)
        this.setState({
          items: response.results,
          isFetching: false
        });
      })
      .catch(error => {
        // finish fetchnig
        this.setState({isFetching: false})
        notification.warning({
          message: error.code,
          description: error.message,
        })
      })
  }

  changeDialog = (e, index) => {
    e.preventDefault()
    this.setState({
      activeIndex: index,
    })
  }

  render() {

    // if your component is while fetching shows a loading to the user
    if(this.state.isFetching) return <div>Loading...</div>;
    // if there is no results shows a msg to the user
    if(this.state.items.length === 0) return <div>there is not items!!!</div>

    const { activeIndex, items } = this.state
    const {
      first_name: firstName,
      last_name: lastName,
      phone,
      email,
      address,
    } = items[activeIndex]

Upvotes: 0

Related Questions