Keeping React component’s state top of mind

I’m impatient

I’ve done this too many times:

// api call to get the contact Luke Skywalker
// process a little data
return (
  <Card>
    <Card.Title>{person.name}</Card.Title>
  </Card>
)

just to get a lovely Can’t read property ‘name’ of undefined error. Yuck.

I fire up the dev console…

Network tab, is the API returning data? ✅. Yup.

Add breakpoint after the API call, do I see data in person? ❌ No, it’s empty…

Hit play in the debugger, it hits the same breakpoint again, now I see data in person

Light dawns on marble head… duh, the API call is async, I have to wait it out.

I could use await on the API, but do I want my front end to wait for an API call to finish? Do my users want to wait? Probably not. Instead, I’ll show something while we wait. A little tweak to my code:

// api call to get the contact Luke Skywalker
// process a little data

return (
  <Card>
    { person &&
      <Card.Title>{person.Name}</Card.Title>
    }
    { !person &&
      <Card.Loader>Loading...</Card.Loader>
    }
  </Card>
)

This change gives my user the instant satisfaction of seeing the page respond immediately and tells them that we’re waiting. AND (more importantly) I no longer have that annoying null error. If person has data, show it, otherwise, assume we’re loading it.

Null error is gone and my component is a little more user friendly. How?

Managing the state of the component

There may be a little confusion here. You may not see a “state” above. If you’ve been neck deep in state management libraries like Redux, you won’t see that state here. Redux and other libraries help manage the state of an app, and enable it to be easily managed/shared across components (among many other benefits). Regardless, we’re talking about the same state, the state of the component.

What is the state of the component?

The state of the component is the given state it can be in at any given time.

I used state to define it, let’s try this:

The state of the component is conditional layout/format/data/view the component is in at any given time.

A component can only be one thing at any given time. I send in data to show Luke Skywalker, then the contact card component will be in a state: showing Luke Skywalker. If his phone number is missing, then the phone icon is hidden, that too is a state.

State can be defined implicitly through the data we want to render or explicitly through objects.

Given my example above, I am implicitly managing the state of the component: there is a state when person is empty, a state when person has data. My component has to handle both since both can happen.

Implicitly managing the state doesn’t always suffice, especially when we’re talking about API calls.

Explicitly managing the state

In addition to implicitly managing the state, we can manage the state explicitly. What if person is empty because there is no data? What if it errors? I can’t get to these states just relying on person itself. I need to know what happened with the API call.

Let’s create a new object, explicitly, to manage our state, and update our component accordingly. Here’s how this new object can manage our component’s state:

  <Card>
    { dataStatus === "Loaded" &&
      <Title>{person.name}</Title>
    }
    { dataStatus === "Loading" &&
      <Loader>Loading...</Loader>
    }
    { dataStatus === "Error" &&
      <Error>Sorry, we had an oopsie...</Error>
    }
    { dataStatus === "Empty" &&
      <Empty>Looks like we're missing something...</Empty>
    }
  </Card>

See the states of my card? Based on the value of dataStatus, we can now show what we need to to the user. We can clearly manage and create states for any and every variation I want! This approach is preferred over the earlier person && check since this gives us 3 states when person is empty: Loading, Error and Empty.

Check it out in realtime, below from codepen, click the buttons below to change the state.

See the Pen Playing with State in React by David Lozzi (@davidlozzi) on CodePen.dark

If you click on the Babel tab above, you’ll see the code running.

  • person is basically ignored and used only when I’m in the right state to actually pull data from it.
  • dataStatus has an initial value, "Loading", and the component responds cleanly and lets the user know what’s going on
  • Click Set to Loaded to complete the API call and show what the component looks like when the state changes
  • Click the other two buttons to check out the state of the component when the API is successful but the data is empty, and when the API errors.

Implicitly Explicit

Weird play on words there 👆. It’s worth noting that you can do both. You can rely on the data for some and rely on an explicit object for others. For example, what if we look at the state of dataStatus being "Loaded". It will render the entire contact card, like:

  <Card>
    { dataStatus === "Loaded" &&
      <Title>{person.name}</Title>
      <Phone>{person.phone}</Phone>
    }
...

What if person.phone is empty? We could create another object to explicitly manage the state for each possible parameter in an object, but that would be a hot mess. Instead, we can use both methods, something like:

  <Card>
    { dataStatus === "Loaded" &&
      <Title>{person.name}</Title>
      { person.phone && <Phone>{person.phone}</Phone> }
    }
...

That should look a little familiar, we used the && method above.

Here we are explicitly showing the "Loaded" state, but then implicitly showing specific fields based on the data values itself.

Make sense?

I hope this helps some and I didn’t make it more confusing. Let me know below if there are questions or something confusing I can help clarify.

Happy coding!

Leave a Reply

Up ↑

%d bloggers like this: