Component state

Component state

For small apps with one or two pieces of variable data, it’s easy enough to store the data at the top level, and pass it to child components via props — just as we’ve done with the fractal tree examples. The thing is, as apps grow, so does the amount of state that needs to be stored. For example, just on the page you’re currently looking at, there’s state for:

  • Whether each individual lesson is complete
  • Whether spoilers are currently focused
  • Whether you’re logged in, and if so, who you are
  • The text you’ve type into each individual demoboard, along with selected tab, whether the console, solution or compiled panels are open, the dimensions of container element, the list goes on…

Storing all of this state at the top level and then passing it down via props would be a nightmare. Luckily, you don’t have to, because you can use React’s useState() function instead!

#A quick syntax quiz

There’s something I want to get out of the way before diving into any details on useState(). The thing is, when you see it in the wild, it’ll usually look something like this:

let [name, setName] = useState('')

So here’s my question for you:

What does the syntax on the left hand side of the above useState() call do?

If you’re not exactly sure, I’ll give you a hint: there’s nothing React-specific about it; it’s just plain old JavaScript. When you’re ready to see the answer, take a look inside the box below.

The above example is just using ES6 Destructuring Assignment. It could be rewritten as so:

let state = useState(1)
let name = state[0]
let setName = state[1]

So in the above example, here’s what happens:

  1. useState() is called.
  2. It returns an array.
  3. Its two values get put into the name and setName variables.

Naturally, you can name the two destructured variables whatever you like. In this case they’re name and setName, but they could just as easily be mousePosition and setMousePosition. So with that out of the way, it’s time to explore the magical world of state!

#The useState() function

What does useState() do? Simple! In fact, the answer is so simple, that I think you’ll be able to answer it yourself after taking a look at the example below.

index.js
styles.css
import React, { useState } from 'react'
import ReactDOM from 'react-dom'

function App() {
  let [name, setName] = useState('')

  return (
    <label>
      <span className="label">Name</span>
      <input
        value={name}
        onChange={event => setName(event.target.value)}
      />
      {!name && "Please enter your name"}
    </label>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))
Build In Progress

Have a play around with the above demo and see if you can figure out what useState() does. Then, once you have an answer, check it in the box below.

In a nutshell, the useState() function lets you store state between renders. It returns an array that holds two values — let’s call them state and setState:

  • The state variable starts with whatever value is passed to useState() itself, and remembers its value between renders.
  • The setState variable holds a function. You can call this function to set a new value for the state — and if the new value differs from the previous one, the component will be re-rendered.

#Try it out

The best way to really understand useState() is to give it a try. So, let’s make a small change to the swaying tree:

You task is to update this demo so that the mouse position is stored with component state instead of global state.

To start, you’ll need to add a top level component to store the state (as useState() can only be called within components). Then, you’ll need to update the mousemove event handler and actually render the state returned by useState(). Here’s the signature of useState() again:

let [state, setState] = useState(initialState)

For the moment, don’t touch anything other than the mouse position — we’ll refactor the rest later in this section.

index.js
FractalTreeBranch.js
FractalHelpers.js
import React from 'react'
import ReactDOM from 'react-dom'
import FractalTreeBranch from './FractalTreeBranch'

// The height and width of the entire window
const { innerHeight, innerWidth } = window

// Store the current mouse position
let mousePosition = {
  x: innerWidth / 2,
  y: innerHeight / 2,
}

function renderFrame() {
  let time = Date.now()
  let fromHorizontalCenter = (innerWidth / 2 - mousePosition.x) / innerWidth
  let fromVerticalCenter = (innerHeight / 2 - mousePosition.y) / innerHeight
  let lean = 0.03 * Math.sin(time / 2000) + fromHorizontalCenter / 4
  let sprout =
    0.3 +
    0.05 * Math.sin(time / 1300) +
    fromVerticalCenter / 5 -
    0.2 * Math.abs(0.5 - fromHorizontalCenter / 2)

  ReactDOM.render(
    <div
      style={{
        position: 'absolute',
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        overflow: 'hidden',
      }}
      onMouseMove={event => {
        mousePosition.x = event.clientX
        mousePosition.y = event.clientY
      }}>
      <FractalTreeBranch lean={lean} size={150} sprout={sprout} />
    </div>,
    document.getElementById('root'),
  )

  // Schedule another frame
  window.requestAnimationFrame(renderFrame)
}

renderFrame()
Build In Progress

How’d you go? It’s important to give this exercise a try, as useState() is one of the foundations of real world React apps. The more you use it, the more it’ll start to make sense. And once you’ve given the exercise a go, let’s discuss what’s happening in a little more detail.

#State and re-renders

In the “How React Works” section, I mentioned that there are two ways to trigger a re-render — and now you’ve seen both of them!

  • You can re-render the entire app with ReactDOM.render()
  • You can schedule a re-render of a single component by updating its state.

So which one should you use? That’s easy:

Where possible, use component state.

The thing about component state is that it’s stored alongside the components that actually use it. This is great for performance:

  • It means that React only needs to re-render the updated component and its children — as opposed to the whole tree.
  • It allows React to batch multiple setState() calls into a single update to improve performance.

The performance improvement enabled by component state is impressive, and is one of the key ideas that makes React possible. But quite aside from the performance benefit, there’s another major reason to use component state.

#State makes components possible

Say you want to make a datepicker, a modal, or a dropdown component. But actually, let’s not do that. Nobody really wants to make another datepicker, modal or dropdown component. And that’s the wonderful thing about state! It makes it possible to encapsulate complex behaviors within reusable, independent components.

The killer feature of component state is that it allows you to encapsulate complex functionality with components.

There’s just one thing… state is necessary for encapsulated functionality, but it is usually not sufficient. To build truly useful components, you also need the ability to declare effects. And happily, hooks make this simple.

Progress to next section.