Skip to main content

Context

Learning Objectives

  • Explain how we simply code using abstraction
  • Understand what Providers and Consumers are and what they do
  • Implement a shared context to avoid prop drilling

Why & when to use Context?

A good analogy of Context vs Props is when we shop online. Props is like buying a product from a retailer instead of the manufactures. Doing so adds one more step and additional overhead/cost to the whole process. However Context is like buying the product directly from the manufacturer.

useContext is one of the 3 Basic Hooks that are included with React v16.8 and, along with useState and useEffect, can be used to build a full React application.

Basic Hooks

  • useState
  • useEffect
  • useContext

Activity - 2min

Let's take a few minutes to review the useContext hook official documentation

Note: When used with useReducer the two 2️⃣ Hooks create a low level version of Redux, which is a state management tool for JavaScript applications and has become synonymous with React state management.


Where Does State Go?

As we've covered before, React has a unidirectional data flow, passing props from parents to children. Components that manage their own state will often pass this information down as props child components.

As apps grow, the tree of components grows with it. Soon the decision to create additional state may require that you pass pass and/or lift state further and farther in the React hierarchy then needed.

How would you answer the following question:


Where should you store and manage single or multiple instances of state in the app?


There are a lot of ways to answer this question - ultimately, it comes down to your own understanding of the app, and your preferences as a developer. But, a generic way to answer this question would be:

Answer

Manage state in the nearest "ancestor" of the components that need to use it.

App-level state

We discussed this briefly when we covered lifting state - when we had two sibling components that needed to share some state, we "lifted" the state up to the parent component.

But, there are times where pieces of state need to be used across the entire app. For instance:

  • When many views & UI components need to know who the currently logged-in user is.
  • When all UI components need to know if the app is in "light" or "dark" mode.

This is one of the times when Context can be useful: when you need to manage app-level state.

Avoiding Prop Drilling

So far, we have been passing props down from parent to child components. In most instances, this is the best way to handle sharing information between components as it keeps this information localized to the parts of your tree that need it.

But, as your tree grows, you'll find yourself passing props down through multiple levels. In the example below sending a single value of state all the way down to the the last child requires passing it down as props through every intermediary Component in the hierarchy.

Use useContent

A much better way would be to provide access to state at a global level and have the child Component access it directly.

Summary: When & Why to use Context

So the two main reasons we should make use of useContext are:

  • Avoiding Prop Drilling
  • Managing App-level state

Context Provider and Consumer Model

When using Context, you'll be dealing with a Provider component and Consumer components.

Providers are components that exist higher up on the tree, sending - or providing - information to other components that are further down. This component must wrap the parts of the tree that it will be communicating with.

Consumers are components that receive - or consume - information from their Provider ancestor. Consumer components use the function as a child pattern to 'extract' these values so we can use them in our own components.


Airbnb Activity - 2min

Lets take a look at AirBnB in DevTools in order to see the Provider/Consumer model in action.


React Router Provider > Consumer Example

Another example of the Provider > Consumer model is React Router. If you haven't already worked on the iStocks App React router assignment then it's something we will get to in the next few lessons.

For now just know that React Router uses the Provider > Consumer model which is visible in React DevTools.

Also another thing to note is that React Router creates and passes the following props using the Provider > Consumer model.

// this is Home - props
{history: {}, location: {}, match: {}, staticContext: undefined}
history: {length: 1, action: "POP", location: {}, createHref: ƒ, push: ƒ,}
location: {pathname: "/", search: "", hash: "", state: undefined}
match: {path: "/", url: "/", isExact: true, params: {}}

Instructor Demo Of Prop Drilling

The Instructor will perform a demo of using prop drilling to pass state from App to it's child Components.

In this demo we will be using the following starter code: React Context CodeSandbox

Here is the Solution Code

Working With React Context

Working with React Context involves setting up context in the parent Component and then consuming it in the child. In order to work with context we will need to do the following:

In the App Component:

  • import createContext
  • create and export an instance of createContext
  • set up a context.Provider
  • provide the data via the context.Provider

In the Child Components:

  • import the useContext
  • import the context from App
  • instantiate useContext to use the imported context from App

App Component

Importing and Using createContext

Before we can work with context we first need to import createContext from React.

App.js
import React, { createContext } from "react";

Now we instantiate and export a new instance createContext.

App.js
export const DataContext = createContext();
console.log("DataContent", DataContext);

This function returns an object with two properties, Provider and Consumer:

> DataContext Object
>
> > $$
> > typeof: Symbol(react.context)
> >
> > Consumer: {$$typeof: Symbol(react.context), _context: {...}, ...}
> >
> > Provider: {$$typeof: Symbol(react.provider), _context: {...}}
> > $$

Setting Up The Context Provider

The Provider is wrapped around the Components that we wish to provide the context. It requires a single prop called value which is assigned the data we wish the child Components to consume.

In this example we will pass the userData that has been imported and stored in state.

App.js
<DataContext.Provider value={userData}>
<ComponentA />
<ComponentE />
</DataContext.Provider>

Child Components

The child Components will now consume the data. Consumers must first import useContext and the Context provided from the parent Component.

ComponentD.jsx
import React, { useContext } from "react";

import { DataContext } from "./App";

useContext

Let's now setup useContext to use the context imported from App.

ComponentD.jsx
const dataContext = useContext(DataContext);

And now we can access that data.

ComponentD.jsx
function ComponentD() {
return (
<div style={style}>
This is Component D
<div>
<div>username: {dataContext.name}</div>
<img class="avatar-image" src={dataContext.img} alt="avatar" />
</div>
</div>
);
}

Here is the final solution code

Resources