React useContext with useReducer
code snippet
A sample from a DEV Community post 1.
# init
npx create-react-app@5.0.1 react-context-with-usereducer
cd react-context-with-usereducer
app-context.js
import React, { useMemo, useReducer, createContext, useContext } from 'react';
import { initialState, contextReducer } from './app-context-reducer';
import contextActions from './app-context-actions';
const AppContext = createContext();
function AppContextProvider({ children }) {
const [state, dispatch] = useReducer(contextReducer, initialState);
const value = useMemo(() => [state, dispatch], [state]);
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}
function useAppContext() {
const context = useContext(AppContext);
if (context === undefined) {
throw new Error('useAppContext must be used within a AppContextProvider');
}
const [state, dispatch] = context;
const appContextAction = contextActions(dispatch);
// const appContextSelector = contextSelectors(state);
return { state, appContextAction };
}
export { AppContextProvider, useAppContext };
app-context-types.js
export const OPEN_NAV_MENU = 'OPEN_NAV_MENU';
export const CLOSE_NAV_MENU = 'CLOSE_NAV_MENU';
export const COLLAPSE_NAV_MENU = 'COLLAPSE_NAV_MENU';
app-context-reducer.js
import * as actionTypes from './app-context-types';
// Define the initial state for the context
export const initialState = {
isNavMenuClose: false,
};
// Define the reducer function for the context
export function contextReducer(state, action) {
switch (action.type) {
// Handle the OPEN_NAV_MENU action
case actionTypes.OPEN_NAV_MENU:
return {
...state,
isNavMenuClose: false,
};
// Handle the CLOSE_NAV_MENU action
case actionTypes.CLOSE_NAV_MENU:
return {
...state,
isNavMenuClose: true,
};
// Handle the COLLAPSE_NAV_MENU action
case actionTypes.COLLAPSE_NAV_MENU:
return {
...state,
isNavMenuClose: !state.isNavMenuClose,
};
// Throw an error for any unhandled action types
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
app-context-actions.js
import * as actionTypes from './app-context-types';
// Define a function that returns an object with context actions
const contextActions = (dispatch) => {
return {
navMenu: {
// Action for opening the navigation menu
open: () => {
dispatch({ type: actionTypes.OPEN_NAV_MENU });
},
// Action for closing the navigation menu
close: () => {
dispatch({ type: actionTypes.CLOSE_NAV_MENU });
},
// Action for toggling (collapsing/expanding) the navigation menu
collapse: () => {
dispatch({ type: actionTypes.COLLAPSE_NAV_MENU });
},
},
};
};
export default contextActions;
App.js
import logo from './logo.svg';
import './App.css';
import { AppContextProvider } from './app-context';
import Sample from './Sample';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<AppContextProvider>
<Sample />
</AppContextProvider>
<br/>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
index.css
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
Sample.js
import { useAppContext } from './app-context';
function Sample() {
const { state: stateApp, appContextAction } = useAppContext();
const { isNavMenuClose } = stateApp;
const { navMenu } = appContextAction;
const onCollapse = () => navMenu.collapse();
const onOpen = () => navMenu.open();
const onClose = () => navMenu.close();
return (
<>
<span>{'my state:'}</span>
<span>{`${JSON.stringify(stateApp)}`}</span>
<button type="button" onClick={onOpen}>Open</button>
<button type="button" onClick={onClose}>Close</button>
<button type="button" onClick={onCollapse}>Collapse</button>
</>
);
}
export default Sample;
After creating all files, the repo should look like below.
|-src
| |-app-context.js
| |-app-context-types.js
| |-app-context-reducer.js
| |-app-context-actions.js
| |-App.js
| |-App.css
| |-index.js
| |-index.css
| |-Sample.js
# begin by typing:
cd react-context-with-usereducer
npm start
# starts the development server.
npm start
# bundles the app into static files for production.
npm run build
# starts the test runner.
npm test
# removes this tool and copies build dependencies, configuration files
# and scripts into the app directory. If you do this, you can’t go back!
npm run eject
# GET request
curl localhost:3000