Skip to main content

6.2 - Passing Data Deeply with Context

Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many many components.

Wouldn’t it be great if there were a way to “teleport” data to the components in the tree that need it without passing props?


Context: an alternative to passing props

Context lets a parent component provide data to the entire tree below it, instead of using props. Consider this Heading component that accepts a level for its size:

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
return (
<Section>
<Heading level={1}>Title</Heading>
<Heading level={2}>Heading</Heading>
<Heading level={3}>Sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={5}>Sub-sub-sub-heading</Heading>
<Heading level={6}>Sub-sub-sub-sub-heading</Heading>
</Section>
);
}

Let’s say you want multiple headings within the same Section to always have the same size. If you were to do this with props, you might pass a level prop to each <Heading> separately:

<Section>
<Heading level={3}>About</Heading>
<Heading level={3}>Photos</Heading>
<Heading level={3}>Videos</Heading>
</Section>

It would be nice if you could pass the level prop to the <Section> component instead and remove it from the <Heading>. This way you could enforce that all headings in the same section have the same size:

<Section level={3}>
<Heading>About</Heading>
<Heading>Photos</Heading>
<Heading>Videos</Heading>
</Section>

But how can the <Heading> component know the level of its closest <Section>? You can'd do it with props alone. This is where context comes into play.

Context lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props.

For this example, you will have Section provide a context. (You can call it LevelContext, since it’s for the heading level.) Heading will read from LevelContext to know what level value to use.


Step 1: Create the context

First, you need to create the context. You’ll need to export it from a file so that your components can use it:

import { createContext } from 'react';

export const LevelContext = createContext(1);

The only argument to createContext is the default value. We've set to 1. (Here, 1 refers to the biggest heading level).


Step 2: Use the context

In your file containing the Heading component, import the useContext Hook from React and your context:

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

Change the Heading component to have it read the value from the context you just created:

export default function Heading({ children }) {
const level = useContext(LevelContext);
// ...
}

useContext is a Hook. Just like useState and useReducer, you can only call a Hook immediately inside a React component (not inside loops or conditions). useContext tells React that the Heading component wants to read the LevelContext.

Now that the Heading component doesn’t have a level prop, you don’t need to pass the level prop to Heading in your JSX like this anymore:

<Section>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
</Section>

Update the JSX so that it’s the Section that receives it instead:

<Section level={4}>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>

Step 3: Provide the context

So far, the Heading components will all use the default level of 1 we set previously. We now need to tell React that each Section component provides context to its children Heading components.

The Section component currently renders its children:

export default function Section({ children }) {
return (
<section className="section">
{children}
</section>
);
}

Wrap them with a context provider to provide the LevelContext to them:

import { LevelContext } from './LevelContext.js';

export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
);
}

This tells React: “if any component inside this <Section> asks for LevelContext, give them this level.” The component will use the value of the nearest <LevelContext.Provider> in the UI tree above it.


Using and providing context from the same component

Since context lets you read information from a component above, each Section could read the level from the Section above, and pass level + 1 down automatically. Here is how you could do it:

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Section({ children }) {
const level = useContext(LevelContext);
return (
<section className="section">
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}

With this change, you don’t need to pass the level prop either to the <Section> or to the <Heading>:

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
return (
<Section>
<Heading>Title</Heading>
<Section>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Section>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Section>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
);
}
note

It’s not always better to use context instead of props! Sometimes props can help with readability if your data flow is very complex by making it explicit which component passed information to which component.