React assembles an application from multiple components.
How can we share the same data, such as login status, between components that are far away from each other?
The key is that "many" components are "far" apart from each other. If it's close, you can just pass it as props.
However, if you use props, there are a lot of components that needlessly pass through without using them in the middle when data is used globally. This makes data state management and maintenance difficult.
What you need at this point is contextAPI. contextAPI manages data in a component-independent space, so you can get or set data wherever you really need it.
When using contextAPI, remember 3 factors:
-
Context Store : Where you initialize contextimport { createContext } from 'react'; // Pass a value to the parameter otherwise use <Context.Provider>. const ColorContext = createContext({ color: 'black' }); export default ColorContext;
-
Context Consumer : Where you actually use contextimport ColorContext from '../contexts/color'; const ColorBox = () => { return ( <ColorContext.Consumer> {/* Function as a child(Render Props) */} {(value) => ( <div style={{ width: 64, height: 64, background: value.color }} /> )} </ColorContext.Consumer> ); }; export default ColorBox;
-
Context Provider : Where your app manage state globallyimport ColorBox from './components/ColorBox'; import ColorContext from './contexts/color'; function App() { return ( <ColorContext.Provider value={{ color: 'tomato' }}> <div> <ColorBox /> </div> </ColorContext.Provider> ); } export default App;
Either
React.createContextor<Provider>(which is derived from the former) can specify the initial state of the context.
To useReact.createContext, pass a value to the parameter,
and to use<Provider>, pass a value to the value props.
And values don't necessarily have to be static - functions can be used as well.
Or you can set all of your context logics in the same file.
Here we define Provider together with React.createContext.
And the logic in Provider has the same form as the argument of React.createContext (which is used for global state initialization).
By unifying these two structures, we can understand the internal structure of the context more intuitively.
Also, as we saw above, even if Provider is omitted, no error occurs.
-
Context Store : Where you initialize contextimport { createContext, useState } from 'react'; const ColorContext = createContext({ state: { color: 'black', subcolor: 'tomato' }, actions: { setColor: () => {}, setSubcolor: () => {}, }, }); const ColorProvider = ({ children }) => { const [color, setColor] = useState('black'); const [subcolor, setSubcolor] = useState('tomato'); const value = { state: { color, subcolor }, actions: { setColor, setSubcolor }, }; return ( <ColorContext.Provider value={value}>{children}</ColorContext.Provider> ); }; const { Consumer: ColorConsumer } = ColorContext; export { ColorConsumer, ColorProvider }; export default ColorContext;
-
Context Consumer : Where you actually use contextimport { ColorConsumer } from '../contexts/color_advanced'; const ColorBox = () => { return ( <ColorConsumer> {/* Function as a child(Render Props) */} {(value) => ( <> <div style={{ width: 64, height: 64, background: value.state.color }} /> <div style={{ width: 32, height: 32, background: value.state.subcolor }} /> </> )} </ColorConsumer> ); }; export default ColorBox;
-
Context Provider : Where your app manage state globallyimport ColorBox from './components/ColorBox'; import { ColorProvider } from './contexts/color_advanced'; function App() { return ( <ColorProvider> <div> <ColorBox /> </div> </ColorProvider> ); } export default App;
Now let's put the concepts we've learned into practice.
Clicking on a color in the following color palette will change the color of the two squares accordingly.
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
const SelectColors = () => {
return (
<div>
<h2>Choose a color.</h2>
<div style={{ display: 'flex' }}>
{colors.map((color) => (
<div
key={color}
style={{
background: color,
width: 24,
height: 24,
cursor: 'pointer',
}}
/>
))}
</div>
<hr />
</div>
);
};
export default SelectColors;Clicking the left mouse button will change the color of the large square, and clicking the right mouse button will change the color of the small square.
To do this, we will import the actions of the Consumer that we created in advance.
To actually "consume" the Context, as shown below, let's import a Consumer and write logic in it.
And note that in Consumer, it is written in the form of "Function as a child".
Consumer is a bit smarter than you think.
This is because the value we passed as an argument of React.createContext or value props of the Provider is remembered as it is.
So you can make your code simpler using destructuring like this:
import { ColorConsumer } from '../contexts/color_advanced';
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'];
const SelectColors = () => {
return (
<div>
<h2>Choose a color.</h2>
<ColorConsumer>
{({ actions }) => (
<div style={{ display: 'flex' }}>
{colors.map((color) => (
<div
key={color}
style={{
background: color,
width: 24,
height: 24,
cursor: 'pointer',
}}
onClick={() => {
actions.setColor(color);
}}
onContextMenu={(e) => {
e.preventDefault();
actions.setSubcolor(color);
}}
/>
))}
</div>
)}
</ColorConsumer>
<hr />
</div>
);
};
export default SelectColors;While onClick is an event handler that detects a left mouse click, onContextMenu is an event handler that detects a right mouse click.
Since onContextMenu outputs the browser menu by default, let's use preventDefault so that the function that changes your color can be executed instead of the browser menu.
Now we come to the master stage of contextAPI.
So far we've been using the slightly raw API. Function as a child is less intuitive and the code is a bit longer.
In this case, React.useContext of functional component hooks and static contextType of class component help make the code more concise and intuitive.
useContext allows you to replace complex logic like Consumer.
However, since this is hooks, it can only be used in functional components.
- Get State
import { useContext } from 'react'; import ColorContext from '../contexts/color_advanced'; const ColorBox2 = () => { const { state } = useContext(ColorContext); return ( <> <div style={{ width: 64, height: 64, background: state.color }} /> <div style={{ width: 32, height: 32, background: state.subcolor }} /> </> ); }; export default ColorBox2;
- Set State
import { useContext } from 'react'; import ColorContext from '../contexts/color_advanced'; const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; export default function SelectColorsHooks() { const { actions } = useContext(ColorContext); console.log(actions); return ( <div> <h2>Choose a color.(useContext)</h2> <div style={{ display: 'flex' }}> {colors.map((color) => ( <div key={color} style={{ width: 24, height: 24, background: color, cursor: 'pointer', }} onClick={() => actions.setColor(color)} onContextMenu={(e) => { e.preventDefault(); actions.setSubcolor(color); }} /> ))} </div> <hr /> </div> ); }
If contextType is defined as a static property by using the static keyword, an instance of this class can be accessed through the this.context keyword.
To do this, we need to assign our custom Context to a static contextType.
-
Get State
import { Component } from 'react'; import ColorContext from '../contexts/color_advanced'; export default class ColorBoxCC extends Component { static contextType = ColorContext; render() { return ( <> <h4 style={{ marginTop: 64 }}>ColorBox with static contextType</h4> <div style={{ width: 64, height: 64, background: this.context.state.color, }} /> <div style={{ width: 32, height: 32, background: this.context.state.subcolor, }} /> </> ); } }
-
Set State
import { Component } from 'react'; import ColorContext from '../contexts/color_advanced'; const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; export default class SelectColorsCC extends Component { static contextType = ColorContext; render() { console.log(this.context); return ( <div> <h2>Choose a color.(static contextType)</h2> <div style={{ display: 'flex' }}> {colors.map((color) => ( <div key={color} style={{ width: 24, height: 24, background: color, cursor: 'pointer', }} onClick={() => this.context.actions.setColor(color)} onContextMenu={(e) => { e.preventDefault(); this.context.actions.setSubcolor(color); }} /> ))} </div> <hr /> </div> ); } }
As we saw above, useContext hooks and static contextType make code more beautiful in functional and class components, respectively.
But the big downside is that you can only have one contextType per class.
So let's use a combination of functional components and useContext hooks as recommended by the react team.