What is the scope of React Context?

Viewed 1526

I am not clear on where Context can be created in a React app. Can I create context anywhere in the component tree, and if so, is that context only scoped to children of where it is created? Or is Context inherently global?

Where in the documentation can I find this?

Case: I'm reusing a component multiple times on a page and would like to use context to handle data for sub-components, but that context needs to be unique to each sub-tree.

3 Answers

There are 2 separate things: The context object, and the context provider.

The context object is created once globally (with an optional default value, which is global if no provider was passed from a component parent):

const FontContext = createContext('Courier');

While a context provider actually passes down its own local override of the context, which only applies to its children:

<FontContext.Provider value='Consolas'>
  {children}
</FontContext.Provider>

The interesting part is that contexts cascade:

<>
  <MyFontConsumer /> // value is 'Courier', the default
  <FontContext.Provider value='Consolas'>
    <MyFontConsumer /> // value is 'Consolas'
    <FontContext.Provider value='TimesRoman'>
      <MyFontConsumer /> // value is 'TimesRoman'
    </FontContext.Provider>
    <MyFontConsumer /> // value is 'Consolas'
  </FontContext.Provider>
</>

To consume the context in your component you can use the useContext() hook, (you can use the special <FontContext.Consumer> component or MyClassComponent.contextType instead) which requires you to pass the same object.

My preferred way to avoid having to pass around the context object is to keep them all in the same component and export a custom hook:


const FontContext = createContext('Courier')

export function FontProvider({value, children}) {
  return <FontContext.Provider value={value}>
    {children}
  </FontContext.Provider>
}

export function useFont() {
  return useContext(FontContext)
}

Now you can use it in your component:

import {FontProvider, useFont} from './FontProvider'

function App() {
  const font = useFont() // value is 'Courier', the default

   return <FontProvider value='TimesRoman'>
      <MyChild />
   </FontProvider>
}

function MyChild() {
  const font = useFont() // value is 'TimesRoman'
  ...
}

I am not clear on where Context can be created in a React app. Can I create context anywhere in the component tree?

A context object can technically be created anywhere with createContext, but to actually set the value to something other than the default you need a provider component. The context will be available via its consumer or the useContext hook in any child components of the provider.

Is that context only scoped to children of where it is created?

If by "created" you mean where the provider is located, yes. However with the new context API you can set a default value, even without a provider.

Or is Context inherently global?

No not inherently, though contexts are often placed within or above the app's root component to become global for all practical purposes.

Where in the documentation can I find this?

Case: I'm reusing a component multiple times on a page and would like to use context to handle data for sub-components, but that context needs to be unique to each sub-tree.

As far as I understand, you have two options here:

  1. Use a single context with different providers overriding it in sub-trees
  2. Have a different context for each sub-tree

This is a good question. I'd like to share something about the scope of the context especially.

Context

Context actually is a "global" idea, because at the time you create it, it's not attached to any fiber/component.

  const UserContext = React.createContext()
  export default UserContext

That's why it's normally exported right away. Why? Since all providers and consumers need to access it in different files under normal conditions. Although you can put them in a same file, that really doesn't show off the context nature. It's made to hold a "global" value that is persisted for the entire application. The underlying value is only storing a temporary one as the scope input argument described below. The real value is hidden behind the scene, because it keeps changing depending on the scope.

Scope

Context might be created long before the hook. There's no other way for React to send a data into certain distance deeper without showing up in the props. And a context is designed similar to a function, internally React uses a stack to pop/push context as it enters or leaves a provider. Therefore you'll see similar behavior when the same context provider is nested like below.

const Branch = ({ theme1, theme2 }) => {
  return (
    <ThemeContext.Provider value={theme1}>
      // A. value = theme1
      <ThemeContext.Provider value={theme2}>
        // B. value = theme2
      </ThemeContext.Provider>
      // C. value = theme1
    </ThemeContext.Provider>
  )
}

If you treat each ThemeContext as a function with input argument value, then it's clear what are the values for locations A, B and C. In a way, a context is no different as a function :) This is also a good example to show you the prop value isn't the same value that the context ThemeContext is tracking, because otherwise you'll end up with a fixed value, either theme1 or theme2. Behind the scene, there's a global mechanism :)

NOTES:

Context is designed to be very powerful, however due to the over-rendering issue, it's never reached to that level. But i guess there's no other replacement in React to share variables, therefore there's tendency that it'll become popular again, especially with the introduction of useContextSelector under proposal. I put some explanation to above in a blog here, https://windmaomao.medium.com/react-context-is-a-global-variable-b4b049812028

Related