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:
- Create a context.
- Set the context's value through the Provider.
- 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.)
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.
Thank you.