useContext(): a React hook that's an obvious win

Hooks are a brand new API with a lot of hype. But don't let that fool you – useContext() is incredibly useful.

Earlier today, the React team released React 16.8, and along with it the eagerly awaited hooks API.

If you haven’t yet heard, React’s hooks API opens a up a whole new way of writing components - and the internet is abuzz with talk of the possibilities. Of course, with such a major change, many people are also asking the question: do we really need this new API?

In this article, I’m going to skip this question and instead try to answer something simpler and more useful: are there any situations where hooks are just obviously better? My answer is yes, and I think you’ll agree after seeing just how much useContext improves readability.

#Context 😕

Since React’s beginnings, one of the least satisfying parts of using it has been has been the process of passing data through many levels of your component tree. Initially, this could only be accomplished by prop drilling, i.e. manually passing props through every level of your tree. This was obviously cumbersome, so the React team give us an experimental Context API to work with, before releasing an official Context API early last year. The thing is, even the official Context API has its quirks.

After consuming data from a Context, you’ll typically only have access to the consumed data within a render function, as seen in this example.

App.js
index.js
import React from 'react'

const CurrentRoute = React.createContext({ path: '/welcome' })

export default function App() {
  return (
    <CurrentRoute.Consumer>
      {currentRoute => 
        currentRoute.path === '/welcome' &&
        "Welcome!"
      }
    </CurrentRoute.Consumer>
  )
}
Build In Progress

In the above example, only a single piece of data is being consumed. And honestly, this looks ok. It’s easy enough to understand what’s going on. But imagine for minute that you want to consume values from three Context objects:

App.js
index.js
import React from 'react'

const CurrentRoute = React.createContext({ path: '/welcome' })
const CurrentUser = React.createContext(undefined)
const IsStatic = React.createContext(false)

export default function App() {
  return (
    <CurrentRoute.Consumer>
      {currentRoute =>
        <CurrentUser.Consumer>
          {currentUser =>
            <IsStatic.Consumer>
              {isStatic =>
                !isStatic &&
                currentRoute.path === '/welcome' &&
                (currentUser
                  ? `Welcome back, ${currentUser.name}!`
                  : 'Welcome!'
                )
              }
            </IsStatic.Consumer>
          }
        </CurrentUser.Consumer>
      }
    </CurrentRoute.Consumer>
  )
}
Build In Progress

While this second example works, it’s starting to look unwieldy. You can see that there’s a callback pyramid starting to form – and the more context you consume, the worse it’s gonna get. Unless, that is, you use the new useContext() hook.

#useContext() 😆

Starting with React 16.8, you now have useContext(): a new, simpler way to consume data from multiple contexts. Here’s how you’d use it to simplify the above example:

App.js
index.js
import React, { useContext } from 'react'

const CurrentRoute = React.createContext({ path: '/welcome' })
const CurrentUser = React.createContext(undefined)
const IsStatic = React.createContext(false)

export default function App() {
  let currentRoute = useContext(CurrentRoute)
  let currentUser = useContext(CurrentUser)
  let isStatic = useContext(IsStatic)

  return (
    !isStatic &&
    currentRoute.path === '/welcome' &&
    (currentUser
      ? `Welcome back, ${currentUser.name}!`
      : 'Welcome!'
    )
  )
}
Build In Progress

Neat, huh? But before moving on, I want you to try and imagine something for a minute.

Imagine that both of the above code examples are boring old APIs that you’ve been using for years.

Have you gotten in that frame of mind? Great! So I want to ask you a question:

Which API is clearer?

To me, the answer seems obvious. The second example wins the readability contest hands down. To bring back an old React cliché - it’s much easier to reason about.

Ok, so let’s come back to the read world for a moment (thanks for going through this exercise with me!) It’s February 2019, and hooks are brand spankin' new. They’re a big change, and there’s going to be a lot of uncertainty around them. But you now have at least one solid answer: hooks are definitely an improvement for consuming context.

So the next question is, what else are hooks good for? I’m as keen to find out as you are, and I’ll be making sure to share what I learn on this blog. Don’t want to miss it? Join Frontend Armory now to stay in the loop — it’s free!

In the meantime, if you’re interested in learning hooks then there’s a few resources you can check out:

Thanks so much for reading — it’s great to know that there are people like you who find this useful enough to read all the way to the end 😊 So until next time, happy engineering!

About James

Hi! I've been playing with JavaScript for over half my life, and am building Frontend Armory to help share what I've learned along the way!

Tokyo, Japan