React Context

Through this post, we will briefly look at the concept of React's Context and its usage, and then create a simple component using Context in practice.

1. What is Context?

When I first started developing with React, I encountered some discomfort when passing data between components. This was because the parent component had to "directly" pass data to the child components. If multiple components needed to use the same data, it was inconvenient to pass it through each component's props, and in some cases, it wasn't even possible to pass it via props due to the component structure.

This inconvenience can be resolved by using Context. Context provides a way to manage and share data globally. By using Context, the parent component can store and manage the data, and child components can access the value of that Context without needing to pass props manually.

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

The operation of Context can be summarized as follows:

  1. Create a context.
  2. Set the context's value through the Provider.
  3. The components subscribing to the context can retrieve and use the data from the context.

The created context will look for the closest and most appropriate Provider in the parent components when the subscribing component renders. It will then receive the value provided by that Provider and pass it to the subscribing component. This completes the data passing through Context. This data passing is not one-time; if the Provider changes the context value, the components subscribing to the context will re-render with the new value.

2. Using Context

Let's review the three steps mentioned above with code examples.

1) Creating Context - React.createContext

We create a Context using React.createContext.

const FirstContext = React.createContext(defaultValue);

When creating a Context, you provide a defaultValue, which will serve as the fallback if a value isn't provided through the Context.Provider that will be explained later.

2) Setting Value - Context.Provider

The Context.Provider is used to pass values to child components.

<FirstContext.Provider value={{ hello: "Hello", hi: "Hi" }} />

The value provided to the Context.Provider will be passed to child components that use Context.Consumer. The Context.Provider can be nested, and in case of nested Providers, the closest one to the subscribing component will provide its value.

As mentioned earlier, when the value of the Provider changes, the components subscribed to the context will re-render with the new value.

3) Subscribing - Context.Consumer / contextType

There are two ways to subscribe to a Context.
The first way is by using Context.Consumer.

<FirstContext.Consumer>
  {(value) => (
    <div>
      {value.hello} {value.hi}
    </div>
  )}
</FirstContext.Consumer>

The second way is by using contextType.

class HiHello extends React.Component {
  render() {
    const value = this.context;
    
    return (
      <div>
        {value.hello} {value.hi}
      </div>
    );
  }
}
HiHello.contextType = FirstContext;

Or you can also do it like this.

class HiHello extends React.Component {
  static contextType = FirstContext;
  context!: React.ContextType<typeof FirstContext>; // Type inference is possible
  
  render() {
    const value = this.context;
    
    return (
      <div>
        {value.hello} {value.hi}
      </div>
    );
  }
}

The value of the Context.Provider set with contextType can be accessed using this.context.
With contextType, only the value of the closest Context.Provider is available. If you need to use values from multiple Providers, you should use Context.Consumer.

3. Example

In front-end development, Context is typically used when a component has sub-components. When designing a component, it is common to have one component with multiple sub-components for usability. In such cases, components often need to share data, but passing this data via props can be cumbersome.

To illustrate this, let's create a simple Modal component as an example. Modal components, like common alert and confirm dialogs, typically consist of text and a button at the bottom. Usually, they open when a triggering button is clicked and close when the "close" button is pressed.

The reason Context is needed for a Modal component is that the "open" state of the modal needs to be shared across components.
Let's create a simple Modal using Material UI's Dialog. (You can refer to the Material UI Dialog API for more details.)

Simple Modal Behavior
Modal Component structure

We design the Modal component structure as shown below.

<SampleModal>
  {/* Modal Content */}
  <SampleModal.Content>
    Sample Modal!
  </SampleModal.Content>

  {/* Close Button */}
  <SampleModal.Actions />
</SampleModal>

1) Creating Context

We create a context with a method to change the modal's open state to false (close the modal).

// context/ModalContext.ts
import React from 'react';

export type ModalContextModel = {
  close: () => void;
}

const ModalContext = React.createContext<ModalContextModel>({
  close: () => {},
});

export default ModalContext;

2) Setting Value with Provider

The ModalContainer component, which will be the top-level of the Modal, provides the context through the Provider.

// ModalContainer.tsx
interface State {
  open: boolean;
}

class ModalContainer extends React.Component<Props, State> {
  state: State = {
    open: false,
  };

  getContext(): ModalContextModel {
    return {
      close: this.close,
    };
  }

  open() {
    this.setState({ open: true });
  }

  close() {
    this.setState({ open: false });
  }

  render() {
    const { children } = this.props;
    const { open } = this.state;

    return (
      <ModalContext.Provider value={this.getContext()}>
        <Button variant="contained" color="primary" onClick={this.open}>
          Trigger
        </Button>
        <Dialog open={open}>{children}</Dialog>
      </ModalContext.Provider>
    );
  }
}

export default ModalContainer;

3) Using Context

We will use contextType to subscribe to the context and implement the close button functionality for the modal.

// sub-comp/ModalActions/ModalActionsView.tsx
class ModalActionsView extends React.Component {
  static contextType = ModalContext;
  context!: React.ContextType<typeof ModalContext>;

  onClick(event: React.MouseEvent<HTMLButtonElement>) {
    this.context.close();
  }

  render() {
    return (
      <DialogActions>
        <Button onClick={this.onClick}>
          Close
        </Button>
      </DialogActions>
    );
  }
}

export default ModalActionsView;

You can view the full code here:

We have learned about the context above. Data management is possible not only through React's Context but also through other libraries like Redux and Mobx. You can find detailed information about Mobx in the video below.

[ React + MobX ] What is React?
React에 대한 기본적인 개념과 구조에 대해서 설명합니다.#React #MobX #리액트 #SPA

Thank you.