Context API

By Syncfusion®, Inc. · Last updated

Why context exists, prop drilling, creating context, Providers, reading with useContext, sharing state across components, when to use context, global state basics, a theme example and performance tips.

Purpose of the Context API

The Context API is a React feature that lets a parent share a value with every component below it, without passing it as a prop through each level.

It is used for data that many components need, such as the logged-in user, the theme, or the language of the app.

Real-World Example

A bookshop app has a light and a dark theme. The header, the book cards and the footer all need to know the current theme, and they are many levels below App.

Without context, every component in between must receive the theme as a prop only to pass it on. With context, App shares the theme once, and each component that needs it reads it directly.

Context shares a value with all components inside a Provider. Any of them can read it directly, at any depth.

Prop Drilling

Prop drilling means passing a prop through many components that do not use it, just so it can reach a component deep down.

Prop drilling: user is passed through Page, Sidebar and Menu, which do not use it, to reach UserBadge

App has user

Page passes user

Sidebar passes user

Menu passes user

UserBadge uses user

Three components pass user along without using it. This is called prop drilling.

It works, but look at Page, Sidebar and Menu. Each one takes user and passes it on, without using it. Problems with this:

  • More code — Every middle component needs the prop and its type.
  • Hard to change — Adding a new value means editing every component on the way.
  • Hard to read — It is not clear which components really use the value.

Context solves this.

Creating Context

Using context takes three steps.

Three steps of context: createContext, wrap with a Provider, read with useContext

1. createContext make the context

2. Provider share a value

3. useContext read the value

Create it once, provide it near the top, and read it anywhere below.

The first step is to create the context with createContext. You do this once, in its own file, and export it.

import { createContext } from "react";

interface User {
  name: string;
}

export const UserContext = createContext<User | null>(null);
  • createContext makes a new context.
  • <User | null> is the type of the shared value: a user, or null when nobody is signed in.
  • null is the default value. A component gets it only if there is no Provider above it.

Context Provider

The second step is to provide a value. Wrap part of your app in the context's Provider, and give it a value.

<UserContext.Provider value={user}>
  <Page />
</UserContext.Provider>

Every component inside the Provider, at any depth, can now read user. Components outside it cannot.

Put the Provider as low as you can, but above every component that needs the value. For app-wide data, that is usually near the top, around App's content.

Reading Context with useContext

The third step is to read the value. In any component inside the Provider, call useContext with the context.

const user = useContext(UserContext);

Here is the drilling example again, with context. The middle components now pass nothing.

With context, the Provider sends user straight to UserBadge. The middle components pass nothing

App the Provider

Page

Sidebar

Menu

UserBadge useContext

context: no props needed in between

The Provider shares the value. Any component inside it can read it directly.

Page, Sidebar and Menu have no props now. UserBadge reads the user straight from the context.

Look at this line in UserBadge:

{user ? user.name : "guest"}

The type is User | null, so user may be null. The ? : shows "guest" in that case.

Try removing the <UserContext.Provider> lines in App and press ▶ Run. The badge shows "guest", because without a Provider you get the default value, null.

Sharing Data Across Components

Context can share state, not only fixed values. Put the state in the component that holds the Provider, and pass both the value and a function to change it.

Any component inside can then read and change the shared state.

Pick हिन्दी from the list. The header and the cart message both change. LanguagePicker changed the shared state, and every reader updated.

Look at the Provider:

<LanguageContext.Provider value={{ language, setLanguage }}>

The value is an object with two parts: the current language, and setLanguage to change it. The double braces are an object { } inside a TSX expression { }.

When to Use Context

Context is useful, but it is not for every piece of data.

Use context when:

  • many components at different levels need the same value, and
  • the value is app-wide, such as the user, theme, language or cart.

Do not use context when:

  • only one or two nearby components need the value. Pass props instead.
  • the value changes very often, such as text typed in a box. Every reader would re-render on every key press.

Try props first. If you pass the same prop through three or more components that do not use it, consider context.

Global State Basics

Global state is state that the whole app can reach. With context, you make global state in three steps:

  1. Keep the state with useState (or useReducer) in a component near the top.
  2. Share it, and the functions that change it, through a Provider.
  3. Read it anywhere with useContext.

A clean way to do this is to put all three in one file: the context, a Provider component, and a small custom hook to read it.

Click Add on the books. The cart icon and the checkout line both update. Here is why this pattern is useful:

  • App only wraps everything in <CartProvider>. It does not hold any cart code.
  • Each component calls useCart() and takes only what it needs.
  • If someone forgets the Provider, useCart shows a clear error message instead of a confusing bug.

Theme Example

A theme decides the colours of the app, such as light or dark. It is a classic use of context, because nearly every component needs it.

Click the button. The page and both cards change colour together, because they all read the same theme from context.

Context Performance Considerations

When the Provider's value changes, every component that reads that context re-renders. This is usually fine. In big apps, it can make things slow.

When the Provider value changes, every component that reads the context re-renders

Provider value changes

re-renders

re-renders

re-renders

Every reader of a context updates together. Keep context values small and stable.

Clear the console, then click Change context. Only "Header" and "Cart" render again, because they read the context. NotReader does not.

memo tells React to skip a component when its props have not changed. You will learn it in a later lesson.

Tips to keep context fast:

  • Split contexts by topic. Keep ThemeContext and CartContext separate, so a cart change does not re-render theme readers.
  • Do not put fast-changing values in context, such as typed text or mouse position.
  • Keep the Provider low in the tree, around only the components that need it.

Common mistakes

MistakeWhat you seeFix
Forgetting the ProviderYou always get the default valueWrap the components in <MyContext.Provider value={...}>
Reading context in a component outside the ProviderYou get the default valueMove the component inside the Provider
Passing value={language} but reading { language }language is undefinedPass and read the same shape: value={{ language }}
Creating the context inside a componentA new context every render, so readers lose the valueCreate it once, at the top of a file
One big context for everythingSmall changes re-render many componentsSplit it into several small contexts
Using context for data only one child needsExtra code for no benefitPass a prop instead

Check Yourself

Exercises

What to Remember

  • Context shares a value with every component inside its Provider, so you avoid prop drilling.
  • Three steps: createContext, wrap with a Provider and a value, then read with useContext.
  • Use context for app-wide data. Split it by topic, and keep fast-changing values out of it.

Next: Client-Side Routing