# The React Context API

> Learn how the React Context API lets you share state across your app without prop drilling, using createContext, a Provider, and the useContext hook.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2025-10-18 | Topics: [React](https://flaviocopes.com/tags/react/) | Canonical: https://flaviocopes.com/react-context-api/

> I'm organizing a [React Masterclass](https://flaviocopes.com/courses/react), I'll launch it on November 4th, 2025

The **Context API** was introduced to allow you to pass state (and enable the state to update) across the app, without having to use props for it.

> I wrote this article in 2018, but it was using the old components-based [React](https://flaviocopes.com/react/) syntax, something no one uses anymore, so today I finally updated it for hooks.

The React team suggests to stick to props if you have just a few levels of children to pass, because it's still a much less complicated API than the Context API.

In many cases, it enables us to avoid using [Redux](https://flaviocopes.com/redux/), simplifying our apps a lot, and also learning how to use React.

How does it work?

You create a context using `React.createContext()`, which returns a Context object:

```js
import { createContext } from 'react'

const MyContext = createContext()
```

The context object contains a `Provider` component and a default value. In modern React, we typically use the `useContext` hook instead of the `Consumer` component.

Then you create a wrapper component that returns a **Provider** component, and you add as children all the components from which you want to access the context:

```js
import { createContext, useContext, useState } from 'react'

const MyContext = createContext()

function Container({ children }) {
  const [something, setSomething] = useState('hey')
  
  return (
    <MyContext.Provider value={{ something, setSomething }}>
      {children}
    </MyContext.Provider>
  )
}

function HelloWorld() {
  return (
    <Container>
      <Button />
    </Container>
  )
}
```

I used Container as the name of this component because this will be a global provider. You can also create smaller contexts.

Inside a component that's wrapped in a Provider, you use the **useContext** hook to access the context:

```js
function Button() {
  const { something } = useContext(MyContext)
  
  return <button>{something}</button>
}
```

You can also pass functions into a Provider value, and those functions will be used by components to update the context state:

```js
function Container({ children }) {
  const [something, setSomething] = useState('hey')
  
  const updateSomething = () => setSomething('ho!')
  
  return (
    <MyContext.Provider value={{ something, updateSomething }}>
      {children}
    </MyContext.Provider>
  )
}

function Button() {
  const { something, updateSomething } = useContext(MyContext)
  
  return (
    <button onClick={updateSomething}>
      {something}
    </button>
  )
}
```

## An Example

Here's a complete example showing how to use Context API with modern React:

```js
import { createContext, useContext, useState } from 'react'

// Create the context
const ThemeContext = createContext()

// Provider component
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light')
  }
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

// Component that uses the context
function ThemedButton() {
  const { theme, toggleTheme } = useContext(ThemeContext)
  
  return (
    <button 
      onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#333' : '#fff'
      }}
    >
      Current theme: {theme}
    </button>
  )
}

// App component
function App() {
  return (
    <ThemeProvider>
      <ThemedButton />
    </ThemeProvider>
  )
}
```

## Multiple Contexts Example

In real applications, you'll often need multiple contexts to manage different aspects of your application state. Here's an example showing how to use multiple contexts together:

```js
import { createContext, useContext, useState } from 'react'

// Theme Context
const ThemeContext = createContext()
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')
  const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light')
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

// User Context
const UserContext = createContext()
function UserProvider({ children }) {
  const [user, setUser] = useState(null)
  const [isLoading, setIsLoading] = useState(false)
  
  const login = async (credentials) => {
    setIsLoading(true)
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))
    setUser({ name: 'John Doe', email: credentials.email })
    setIsLoading(false)
  }
  
  const logout = () => setUser(null)
  
  return (
    <UserContext.Provider value={{ user, login, logout, isLoading }}>
      {children}
    </UserContext.Provider>
  )
}

// Language Context
const LanguageContext = createContext()
function LanguageProvider({ children }) {
  const [language, setLanguage] = useState('en')
  const translations = {
    en: { welcome: 'Welcome', login: 'Login', logout: 'Logout' },
    es: { welcome: 'Bienvenido', login: 'Iniciar sesión', logout: 'Cerrar sesión' }
  }
  
  return (
    <LanguageContext.Provider value={{ language, setLanguage, t: translations[language] }}>
      {children}
    </LanguageContext.Provider>
  )
}

// Component using multiple contexts
function UserProfile() {
  const { theme, toggleTheme } = useContext(ThemeContext)
  const { user, logout, isLoading } = useContext(UserContext)
  const { t, language, setLanguage } = useContext(LanguageContext)
  
  if (isLoading) return <div>Loading...</div>
  
  return (
    <div style={{ 
      padding: '20px', 
      backgroundColor: theme === 'light' ? '#f5f5f5' : '#333',
      color: theme === 'light' ? '#333' : '#fff'
    }}>
      <h2>{t.welcome}, {user?.name || 'Guest'}!</h2>
      
      <div style={{ marginBottom: '10px' }}>
        <button onClick={toggleTheme}>
          Switch to {theme === 'light' ? 'dark' : 'light'} theme
        </button>
      </div>
      
      <div style={{ marginBottom: '10px' }}>
        <button onClick={() => setLanguage(language === 'en' ? 'es' : 'en')}>
          Switch to {language === 'en' ? 'Spanish' : 'English'}
        </button>
      </div>
      
      {user && (
        <button onClick={logout}>
          {t.logout}
        </button>
      )}
    </div>
  )
}

// App with multiple providers
function App() {
  return (
    <ThemeProvider>
      <UserProvider>
        <LanguageProvider>
          <UserProfile />
        </LanguageProvider>
      </UserProvider>
    </ThemeProvider>
  )
}
```

## Why Use Multiple Contexts?

Using multiple contexts is common in real applications for several reasons:

1. **Separation of Concerns**: Each context handles a specific domain (theme, user authentication, internationalization, etc.)

2. **Performance**: Components only re-render when the specific context they use changes, not when unrelated state updates

3. **Maintainability**: It's easier to manage and debug when each context has a single responsibility

4. **Reusability**: Contexts can be composed differently in different parts of your app

5. **Testing**: You can test components with only the contexts they need

6. **Team Development**: Different developers can work on different contexts without conflicts

The key is to keep each context focused on a single concern and avoid creating one massive context that handles everything in your application.
