1

I'm using the SpaceX API to build a personal project. I'm using React Router to dynamically load components, rather than refreshing the whole website.

Here is my LaunchDetails component where I'm trying to output some data:

import React, { Component } from 'react'

class LaunchDetail extends Component {
  state = {
    launch: []
  }

  async componentDidMount () {
    try {
      const res = await fetch(`https://api.spacexdata.com/v3/launches/${this.props.match.params.flight_number}`)
      const data = await res.json()
      this.setState({
        launch: data,
        rocket: data.rocket
      })
    } catch (e) {
      console.log(e)
    }
  }

  render () {
    const { launch, rocket } = this.state

    console.log(rocket)

    return (
      <div>
        <div>
          <h1>{launch.mission_name}</h1>
          <p>SpaceX Flight Number: {launch.flight_number}</p>
          <p>Launched: {launch.launch_year}</p>
          <p>Rocket: {rocket.rocket_name}, {rocket.rocket_type}</p>
        </div>
      </div>
    )
  }
}

export default LaunchDetail

Data one level deep like launch.mission_name is displaying correctly... However, when I try and go down another level, say, rocket.rocket_name (eg: launch.rocket.rocket_name), it throws the above error.

What is strange is that this works in another component, but that is using a different end point (all the data) and I'm mapping through it. Not sure if the way I'm calling the data in this component is to blame or not...

Does anyone have any idea why this could be happening?

EDIT: I've updated the code to be simpler after receiving some comments, error still persists:

import React, { Component } from 'react'

class LaunchDetail extends Component {
  state = {
    launch: []
  }

  async componentDidMount () {
    try {
      const res = await fetch(`https://api.spacexdata.com/v3/launches/${this.props.match.params.flight_number}`)
      const data = await res.json()
      this.setState({
        launch: data
      })
    } catch (e) {
      console.log(e)
    }
  }

  render () {
    const { launch } = this.state

    return (
      <div>
        <div>
          <h1>{launch.mission_name}</h1>
          <p>SpaceX Flight Number: {launch.flight_number}</p>
          <p>Launched: {launch.launch_year}</p>
          <p>Rocket: {launch.rocket.rocket_name}, {launch.rocket_type}</p>
        </div>
      </div>
    )
  }
}

export default LaunchDetail
Andy
  • 13
  • 5
  • 2
    I'm pretty sure it's not caused by "3 level deep" properties, but more so this in your render: `rocket.rocket_name`. The rocket isn't initially defined in state, so `this.state.rocket` will be undefined, and that access subproperties of it will give the error you're geting. You fetch the data in `componentDidMount`, but that lifecycle method is called AFTER the first render. So if you should have a loading state for the rendering (for when data is being fetched) and preferably default values for the state – Jayce444 Feb 16 '20 at 09:21
  • 1
    Try initializing `rocket` in your initial `state = { launch: [] }` – Jacob Feb 16 '20 at 09:22
  • Ok, so I added `rocket` to state, still same error. I've now removed it so that it now references `launch.rocket.rocket_name` and still the error persists... – Andy Feb 16 '20 at 09:27

1 Answers1

0

From here you can find that the react lifecycle methods go in the following order.

componentWillMount --> render --> componentDidMount

You have initialized state with

state = {
  launch: []
}

So on the first render, state.rocket will be undefined.

To fix this, either initialise state.rocket or change componentDidMount to componentWillMount With the former being prefered.

Note componentWillMount is deprecated in version 17.


After OP's edit. You are still initializing launch to []

On the first render launch.rocket will be undefined. Therefore launch.rocket.rocket_name will throw an error.

Either initialise launch to have a rocket field. Or do something like

(launch.rocket || {}).rocket_name, or something else to check that rocket is defined before accessing rocket_name.

Jacob
  • 887
  • 1
  • 8
  • 17
  • Thank you, this is my code: `launch: { rocket: [] }` – Andy Feb 16 '20 at 09:45
  • Hopefully you see this, how come I need to initialise state for `rocket` and any other data in this component and not in a different component where I'm calling all the data? I hope that makes sense... – Andy Feb 16 '20 at 10:26
  • The only reason you will need to initialise state for rocket in this component is for the first render. As you are assuming there will always be a `launch` object with a `rocket` prop that has the `rocket_name` field. – Jacob Feb 16 '20 at 10:56
  • Personally I would go for not initialising rocket in this component, and using something like `(launch.rocket || {}).rocket_name` in my render. This way, if `rocket` is undefined, accessing `rocket_name` will not throw an error and it will just render nothing. After `rocket` is initialized via `componentDidMount` it will render the new `rocket_name` – Jacob Feb 16 '20 at 10:59