Note: React Hooks are still an experimental proposal. This post was written while Hooks were in React 16.7.0-alpha.2
I’ve spent the last several days experimenting the latest React Hooks features. It really has been a blast — so much code deletion when converting from the class stuff. Being a bit newer to React, I haven’t put together any huge applications with the library/framework (I say framework) and so I wasn’t really up to speed with using React Context. I spent a bit of time learning how to use context the classic way, and once I grasped that I decided I’d get a hang of the Hooks’ method: useContext. I wanted to document what I learned along the way by showing a small app I created using it.
Wait…What is Context in React?
Many of the difficulties that arise when creating React applications have to do with state management. If you’re using plain React — the stuff you find in “The Basics” inside the React docs, you keep state inside components. When you want to move it around, you “lift” the state to a common ancestor and pass it down via props. As I’m sure you can imagine, this becomes very complicated very quickly.
Context addresses one variant of these complications. There are some pieces of state that are needed in many locations down the component tree. Stuff like theme selections, user preferences, locale settings, and other things that might affect the look-and-feel of the entire application are what I’m talking about. It would be a tremendous burden if you had a very tall component tree and you had to pass the state through props in every level of that tree, especially if you wanted to change, remove, or add new ones later.
Context solves this by allowing you to create context objects and “providers” that provide that context (state) to any components set up to consume it, via “consumers.” In this example I’m still using providers, as you’ll see below, but instead of setting up consumers, I’ll use useState, a built-in hook inside React 16.7.0-alpha.2.
In my example, I use React’s context API to create a genre context object.
1 2 3 |
import React from "react"; export default React.createContext("fantasy"); |
Whenever this file is imported with
1 |
import GenreContext from "../contexts/GenreContext"; |
you are supplying the importing component with access to that context. In this example, I’m setting a default value to my genre context: fantasy.
I’m going to set up a simple genre-selector component with some radio inputs and some logic to handle setting the genre state for the application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import React, { useState } from "react"; import MoviePane from "./MoviePane"; import GenreContext from "../contexts/GenreContext"; const genreSelector = props => { const [genre, setGenre] = useState("fantasy"); function handleSelectGenre(e) { setGenre(e.target.value); } return ( <div> Select genre: <input defaultChecked type="radio" name="genre" value="fantasy" onClick={handleSelectGenre} /> Fantasy <input type="radio" name="genre" value="scifi" onClick={handleSelectGenre} /> Science Fiction <GenreContext.Provider value={genre}> <MoviePane /> </GenreContext.Provider> </div> ); }; export default genreSelector; |
In this file, I import my context object, GenreContext. I need this so I can provide the rest of my app with the currently selected genre. If a particular component needs the current genre, it will only need to import the GenreContext as well, as we’ll soon see. Now look below the radio buttons. Since I want to provide the child components of this genre selection component, I need to wrap the child component, in this case MoviePane, in a Provider. Along with placing the GenreContext.Provider, I give it a special attribute called value. This is where the stateful genre goes. Whenever setGenre is used, the state updates (also using a React Hook, useState). When the state updates, the provider broadcasts the change to any consumer of that context.
Lets take a look at the MoviePane component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import React, { useContext } from "react"; import MovieSelector from "./MovieSelector"; import GenreContext from "../contexts/GenreContext"; const moviePane = props => { const genre = useContext(GenreContext) === "fantasy" ? "Fantasy" : "Science Fiction"; return ( <div> <h3>Select a {genre} movie!</h3> <MovieSelector /> </div> ); }; export default moviePane; |
This is where we diverge from React’s original context API and replace it with a useContext hook. I import the context object, then assign genre the return value of useContext(GenreContext), which will be whatever the current state is inside the genre selection component. Of course, this is only one child down from the owner of that state, so it’s not very impressive. For this example, I added a movie selector component that will also use the context:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import React, { useContext } from "react"; import GenreContext from "../contexts/GenreContext"; const movieSelector = props => { const genre = useContext(GenreContext); function getFantasyMovies() { return ( <select> <option value="hp1">Harry Potter and the Sorcerer's Stone</option> <option value="hp4">Harry Potter and the Goblet of Fire</option> </select> ); } function getScifiMovies() { return ( <select> <option value="sw4">Star Wars Episode IV: A New Hope</option> <option value="sw5"> Star Wars Episode V: The Empire Strikes Back </option> </select> ); } return ( <div>{genre === "fantasy" ? getFantasyMovies() : getScifiMovies()}</div> ); }; export default movieSelector; |
At this point I am in a grandchild component of the state-owning component, and I’m utilizing that state all the same without passing any props. In this case, we also set a genre constant to the return value of useState(GenreContext), and depending on the value, we render a selector element.
This is pretty cool. Any component downstream of the original state-owning component that provides the context can use that context. Even better, this whole example is inside functional components. State management, propagation, and consumption have all been accomplished without using a single React class component.
What About Redux? Does Context Solve Everything?
At first glance, this all seems like a viable replacement for Redux, but I don’t think that’s the case. With small to medium-sized apps, Redux may be overkill. But as the size of your app grows, Redux tends to keep the complexity much lower than if you were to avoid a state-management library altogether. Context hooks and the context API are not state-management libraries, they’re just tools in the toolbox. I think context is useful for a specific type of state like that mentioned above. I ask myself the question: does the state affect the overall look-and-feel of the application? Then it probably would benefit from context. Otherwise, for smaller apps I’ll keep lifting state up and passing via props and for larger apps I’ll continue to use libraries like Flux or Redux.
Thanks for reading.