SoFunction
Updated on 2025-03-08

Detailed interpretation of React Advanced Features Context Ten Thousand Words

Context provides a way to pass data layer by layer without manually passing it through props.

text

In a typical React application, data is passed to subcomponents from top to bottom through props. However, for fixed types of data used by a large number of components (such as local locale environments, UI themes, etc.), doing so seems very cumbersome and clumsy. Context provides a way to share this type of data between components (components with upper and lower hierarchies). This method does not require you to pass data layer by layer through props manually or explicitly.

When to use Context?

This section talks about the business scenarios applicable to context.

Context is designed for those that can be identified as [data that can be shared within the entire component tree]. For example, currently authenticated user data, UI theme data, current user preference language setting data, etc. For example, in the following code, in order to decorate the Button component, we manually pass a prop called "theme" layer by layer. The path to pass is: App -> Toolbar -> ThemedButton -> Button

class App extends  {
  render() {
    return <Toolbar theme="dark" />;
  }
}
function Toolbar(props) {
  // The Toolbar component must take an extra "theme" prop
  // and pass it to the ThemedButton. This can become painful
  // if every single button in the app needs to know the theme
  // because it would have to be passed through all components.
  return (
    <div>
      <ThemedButton theme={} />
    </div>
  );
}
class ThemedButton extends  {
  render() {
    return <Button theme={} />;
  }
}

Using context, we can skip the intermediate components passing through layers. Now our pass path is like this: App -> Button.

// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = ('light');
class App extends  {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return (
      < value="dark">
        <Toolbar />
      </>
    );
  }
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}
class ThemedButton extends  {
  // Assign a contextType to read the current theme context.
  // React will find the closest theme Provider above and use its value.
  // In this example, the current theme is "dark".
  static contextType = ThemeContext;
  render() {
    return <Button theme={} />;
  }
}

Before you use Context

This section talks about us using context with caution. Before using context, we have to consider whether there is a second technical solution available in the current business scenario. Only when you really can't think of it, use context.

Context is mainly used in this kind of business scenario: a large number of components at different levels of the component tree need to share certain data. In actual development, we should always be respectful of context and use it with caution. Because it is like Pandora's box, once it is opened, it causes many uncontrollable phenomena (it means here that once context is abused, it will make it difficult to reuse many components). Refer to the actual video explanation of React:Enter study

If you just want to avoid the impact of data passing on the intermediate layer components, then component combination is a simpler technical solution than context.

For example, if we have one calledPageThe component that needs to beuserandavatarSizeThese two props are passed to several layers belowLinkComponents andAvatarComponents:

<Page user={user} avatarSize={avatarSize} />
// ... which renders ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... which renders ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... which renders ...
<Link href={}>
  <Avatar user={user} size={avatarSize} />
</Link>

We've worked hard touserandavatarSizeIf these two props are passed, the end is onlyAvatarThe components really use it. This approach seems a bit inefficient and redundant. If, to the futureAvatarIf a component needs to obtain some extra data from the top-level component, you have to manually pass the data in the form of props layer by layer. To be honest, this is really annoying.

Without considering the use of context, another technical solution that can solve this problem is:AvatarThe components are passed as props. In this way, other intermediate components do not knowuserThis prop exists.

function Page(props) {
  const user = ;
  const userLink = (
    <Link href={}>
      <Avatar user={user} size={} />
    </Link>
  );
  return <PageLayout userLink={userLink} />;
}
// Now, we have:
<Page user={user} />
// ... which renders ...
<PageLayout userLink={...} />
// ... which renders ...
<NavigationBar userLink={...} />
// ... which renders ...
{}

With this change, only the top-level componentsPageNeed to knowLinkComponents andAvatarThe component needs to use the two datasets "user" and "avatarSize".

In many scenarios, this "control inversion" mode that reduces the number of props that need to be passed makes your code cleaner and gives the top-level components more control permissions. However, it does not apply to every business scenario. Because this solution will increase the complexity of high-level components and at this cost, it will make the components of low-level homes more flexible. And this flexibility is often excessive.

In the technical solution of "component combination", it is not said that you can only have one child component for a component, and you can let the parent component have multiple child components. Or even set a separate "slots" for each individual child component, as described here.

function Page(props) {
  const user = ;
  const content = <Feed user={user} />;
  const topBar = (
    <NavigationBar>
      <Link href={}>
        <Avatar user={user} size={} />
      </Link>
    </NavigationBar>
  );
  return (
    <PageLayout
      topBar={topBar}
      content={content}
    />
  );
}

This pattern is useful enough for most scenarios where child components need to be separated from their parent components. If the child component needs to communicate with the parent component before rendering, you can further consider using render props technology.

However, sometimes you need to access the same data in different components and levels. In this case, it is better to use context. Context is responsible for centrally distributing your data, and while the data is changed, it can synchronize new data to components at the level below it. In the example given in the first section, using context is simpler than using the "component combination" scheme mentioned in this section. Scenarios that apply to context also include "local preference data" sharing, "UI theme data" sharing, and "cache data" sharing, etc.

Related APIs

const MyContext = (defaultValue);

This API is used to create a context object (here is Mycontext). When React renders a component that has subscribed to this context object, it will be from the closest to the componentProviderThe component reads the current context value.

The default value passed in when creating a context object will only be used when the component does not find the corresponding Provider component in the upper-level component tree. This is very helpful for testing component functions separately from the Provider component. Note: If you provide an undefined value to the Provider component value property, this does not refer to React using defaultValue as the current value value. That is, undefined is still a valid context value.

< value={<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->/* some value */}>

Each context object has its corresponding Provider component. This Provider component enables the Consumer component to subscribe and track context data.

It accepts a property called value. The value of this value attribute will be passed to all the descendants of the Provider component. These Consumer components will be re-rendered when the value of the Provider component changes. This data propagation from the Provider component to its descendants Consumer component will not be affected by the lifecycle method of shouldComponentUpdate (this shouldComponentUpdate should refer to the shouldComponentUpdate of the Cousumer component). Therefore, as long as the parent Provider component is updated, the Consumer component, which is a descendant component, will also be updated.

Deciding whether the value of the Provider component has changed is achieved by comparing new and old values ​​using an algorithm similar to that of the algorithm.

Note: When you pass an object to the value property of the Provider component, the rules used to determine whether the value has changed will cause some problems. See note.

Translator's note: I did not understand the examples given by the official documentation about this API. I don’t know if I understand it wrong or the official documentation is wrong. Anyone who knows how to use it in the new context API? Please give me some advice in the comment section.

class MyClass extends  {
  componentDidMount() {
    let value = ;
    /* perform a side-effect at mount using the value of MyContext */
  }
  componentDidUpdate() {
    let value = ;
    /* ... */
  }
  componentWillUnmount() {
    let value = ;
    /* ... */
  }
  render() {
    let value = ;
    /* render something based on the value of MyContext */
  }
}
 = MyContext;

The contextType static property of a component (class) can be assigned a context object. This allows this component class to consume the context value closest to it. Various lifecycle methods of the component are accessible.

Notice:

Using this API, you can only subscribe to a context object. If you need to read multiple context objects, then you can check out Consuming Multiple Contexts.

If you want to use the experimental features of ES7 public class fields syntax, you can use the static keyword to initialize your contextType property:

class MyClass extends  {
  static contextType = MyContext;
  render() {
    let value = ;
    /* render something based on the value */
  }
}

<>
  {value => /* render something based on the context value */}
</>

The Consumer component is the component responsible for subscribing to the context and tracking its changes. With it, you can initiate a subscription to the context in a function component.

As shown in the above code, the subcomponent requirement of the Consumer component is a function (note that this is not a function component). This function will receive a context value and return a React node. This context value is equivalent to the value property value of the Provider component closest to this Consumer component. If the Consumer component does not have the Provider component corresponding to this context at the above level, the context value received by the function is the defaultValue used when creating the context object.

Note: The "function as a child" mentioned here is what we call the render props mode.

Example

1. Dynamic context

I'm printing () in a certain lifecycle method of the component involved in this example, and the console prints it as an empty object. From the interface, the DOM element button also has no background.

Here is a more complex example of dynamically setting contexts of UI theme types:

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};
export const ThemeContext = (
   // default value
);

import {ThemeContext} from './theme-context';
class ThemedButton extends  {
  render() {
    let props = ;
    let theme = ;
    return (
      <button
        {...props}
        style={{backgroundColor: }}
      />
    );
  }
}
 = ThemeContext;
export default ThemedButton;

import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
// An intermediate component that uses the ThemedButton
function Toolbar(props) {
  return (
    &lt;ThemedButton onClick={}&gt;
      Change Theme    &lt;/ThemedButton&gt;
  );
}
class App extends  {
  constructor(props) {
    super(props);
     = {
      theme: ,
    };
     = () =&gt; {
      (state =&gt; ({
        theme:
           === 
            ? 
            : ,
      }));
    };
  }
  render() {
    // The ThemedButton button inside the ThemeProvider
    // uses the theme from state while the one outside uses
    // the default dark theme
    // I did not see the results mentioned in the above comments.    return (
      &lt;Page&gt;
        &lt; value={}&gt;
          &lt;Toolbar changeTheme={} /&gt;
        &lt;/&gt;
        &lt;Section&gt;
          &lt;ThemedButton /&gt;
        &lt;/Section&gt;
      &lt;/Page&gt;
    );
  }
}
(&lt;App /&gt;, );

2. Update context in embedded components

The underlying components of the component tree often need to update the context value of the Provider component. Faced with this kind of business scenario, you can pass in a function key-value when creating a context object, and then pass it into the Consumer component along with the context:

// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
export const ThemeContext = ({
  theme: ,
  toggleTheme: () => {},
});

import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
  // The Theme Toggler Button receives not only the theme
  // but also a toggleTheme function from the context
  return (
    <>
      {({theme, toggleTheme}) => (        <button
          onClick={toggleTheme}
          style={{backgroundColor: }}>
          Toggle Theme        </button>
      )}    </>
  );
}
export default ThemeTogglerButton;

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends  {
  constructor(props) {
    super(props);
     = () => {
      (state => ({
        theme:
           === 
            ? 
            : ,
      }));
    };
    // State also contains the updater function so it will
    // be passed down into the context provider
     = {
      theme: ,
      toggleTheme: ,
    };
  }
  render() {
    // The entire state is passed to the provider
    return (
      < value={}>
        <Content />
      </>
    );
  }
}
function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}
(<App />, );

3. Consume multiple contexts at the same time

In order to make the re-rendering speed caused by context faster, React requires that our consumption of context be carried out in a separate Consumer component.

// Theme context, default to light theme
const ThemeContext = ('light');
// Signed-in user context
const UserContext = ({
  name: 'Guest',
});
class App extends  {
  render() {
    const {signedInUser, theme} = ;
    // App component that provides initial context values
    //Nesting two context provider components    return (
      &lt; value={theme}&gt;
        &lt; value={signedInUser}&gt;
          &lt;Layout /&gt;
        &lt;/&gt;
      &lt;/&gt;
    );
  }
}
function Layout() {
  return (
    &lt;div&gt;
      &lt;Sidebar /&gt;
      &lt;Content /&gt;
    &lt;/div&gt;
  );
}
// A component may consume multiple contexts
function Content() {
  return (
     // Nested two context Consumer components    &lt;&gt;
      {theme =&gt; (        &lt;&gt;
          {user =&gt; (            &lt;ProfilePage user={user} theme={theme} /&gt;
          )}        &lt;/&gt;
      )}    &lt;/&gt;
  );
}

But if two or more contexts are often consumed together, you have to consider merging them to become a context and creating a render props component that accepts multiple contexts as parameters.

Note

Because context uses reference identity to determine whether re-redner is needed, when you provide a literal javascript object value to the value property of the Provider component, this will cause some performance problems - unnecessary rendering of the consumer component. For example, in the following example code, all consumer components will be re-rendered together when the Provider component is re-rendered. This is because every time the value of the value is a new object.

class App extends  {
  render() {
    return (
     // {something: 'something'} === {something: 'something'}' value is false      &lt;Provider value={{something: 'something'}}&gt;
        &lt;Toolbar /&gt;
      &lt;/Provider&gt;
    );
  }
}

To avoid this problem, we can promote the value of this reference type to the state of the parent component:

class App extends  {
  constructor(props) {
    super(props);
     = {
      value: {something: 'something'},
    };
  }
  render() {
    return (
      <Provider value={}>
        <Toolbar />
      </Provider>
    );
  }
}

Legacy API

React introduced an experimental context API in previous versions. Compared with the context API currently introduced, we call it the old context API. This old API will be supported until the end of React version. But your app is better off upgrading it to the new context API described above. This legacy API will be removed in a major version in the future.

This is the end of this article about the detailed interpretation of React's advanced features of Context. For more related React Context content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!