The link below shows my preferred boilerplate for handling context (you can press the button to toggle the background on or off).
codesandbox link
The boilerplate has the following structure, which follows the philosophy of redux.
├── package.json
└── src
├── App.js
├── child.js
├── context
│ ├── actions.js
│ ├── reducer.js
│ └── store.js
└── index.js
action.js
export const actions = {
SET_BACKGROUND: "SET_BACKGROUND"
};
It lists all the allowed action in the context.
reducer.js
import { actions } from "./actions.js";
export const reducer = (state, action) => {
switch (action.type) {
case actions.SET_BACKGROUND:
return { ...state, background: action.background };
default:
return state;
}
};
This is where actual change is made to the context. Reducer first check action type,. Then based on the action type, it makes corresponding modification to the context state.
store.js
import * as React from "react";
import { reducer } from "./reducer.js";
import { actions } from "./actions.js";
import { originalBackground } from "../child";
export const initialState = {
background: originalBackground
};
export const AppContext = React.createContext();
export const Provider = (props) => {
const { children } = props;
const [state, dispatch] = React.useReducer(reducer, initialState);
const value = {
background: state.background,
setBackground: React.useCallback(
(val) => dispatch({ type: actions.SET_BACKGROUND, background: val }),
[]
)
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
This is where the context state is stored. The context state contains values (e.g. background
) and functions to trigger the modification of such values (e.g. setBackground
). Note that the function itself does not modify anything. It dispatches an action, which will be caught by the reducer for the actual state modification.
App.js
import React from "react";
import { Provider } from "./context/store";
import Child from "./child";
function App() {
return (
<Provider>
<Child />
</Provider>
);
}
export default App;
Wrap the Provider
over the component where the context is accessed. If the context is global to the entire app, Provider
can wrap around App
in index.js
.
child.js
import React, { useContext } from "react";
import { AppContext } from "./context/store";
export const originalBackground =
"https://lp-cms-production.imgix.net/2019-06/81377873%20.jpg?fit=crop&q=40&sharp=10&vib=20&auto=format&ixlib=react-8.6.4";
export default function Child() {
const { background, setBackground } = useContext(AppContext);
const renderSelected = (context) => {
if (context) {
return (
<img
style={{ height: "200px" }}
src={context}
key={context + "Thumbnail"}
alt={"thumbnail of " + context}
/>
);
} else {
return <p>None</p>;
}
};
const toggleBackground = () => {
if (background) {
setBackground(null);
} else {
setBackground(originalBackground);
}
};
return (
<div>
<button onClick={toggleBackground}>
{background ? "Background OFF" : "Background ON"}
</button>
<p>Background:</p> {renderSelected(background)}
</div>
);
}
This shows how context is used in a component.