Skip to content

Instantly share code, notes, and snippets.

@codingwithchris
Last active October 30, 2022 17:55
Show Gist options
  • Save codingwithchris/4c1713bd69884e4591dd5b7272d7252e to your computer and use it in GitHub Desktop.
Save codingwithchris/4c1713bd69884e4591dd5b7272d7252e to your computer and use it in GitHub Desktop.
App State Reducer Context Example -- Easily manage the state of a small application by passing userReducer into Context.
import { createContext, useContext, useReducer, Dispatch } from 'react';
/**
* Manages the state for our entire app
*
** For larger, more complex applications, this pattern can be split into multiiple contexts.
** For example: `form`, and `user` would have theor own contexts, each with their own reducer.
*/
// Init an empty context
export const AppStateContext = createContext({} as AppStateContext);
/**
* Define default application state
*/
export const defaultState: AppState = {
view: 'initial',
form: {
processing: false,
error: false,
errorMessage: '',
},
test: {
result: undefined,
number: undefined,
resultText: undefined,
resultExplanation: undefined,
},
user: {
loggedIn: false,
},
};
/**
* Build a simple reducer to handle state updates
*/
const stateReducer = (state: AppState, action: StateAction): AppState => {
switch (action.type) {
case 'REQUEST_PROCESSING':
return {
...state,
form: {
error: false,
processing: true,
errorMessage: '',
},
};
case 'RESULTS_FOUND':
return {
...state,
view: 'results',
test: action.test,
form: {
error: false,
processing: false,
errorMessage: '',
},
};
case 'RESULTS_NOT_FOUND':
case 'RATE_LIMIT_REACHED':
case 'UNKNOWN_REQUEST_ERROR':
return {
...state,
form: {
processing: false,
error: true,
errorMessage: action.errorMessage,
},
};
case 'RESET_STATE':
return {
...state,
};
case 'LOGOUT_USER':
return {
...state,
};
default:
return {
...state,
};
}
};
/**
* Put useReducer into state so we can use it to read from state and
* dispatch actions seamlessly across out entire app.
*/
export const AppStateProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(stateReducer, defaultState);
return (
<AppStateContext.Provider value={{ state, dispatch }}>
{children}
</AppStateContext.Provider>
);
};
/**
* Make our context with state and dispatch globally available as a simple hook
*/
export const useAppStateContext = () => useContext(AppStateContext);
/**
* Typings
*/
export interface AppState {
view: 'initial' | 'results';
form: {
processing: boolean;
error: boolean;
errorMessage: string;
};
test: {
result: 'positive' | 'negative' | 'pending' | undefined;
number: string | undefined;
resultText: string | undefined;
resultExplanation: string | undefined;
};
user: {
loggedIn: boolean;
};
}
type StateAction =
| { type: 'RESULTS_FOUND'; test: AppState['test'] }
| {
type: 'REQUEST_PROCESSING' | 'RESET_STATE' | 'LOGOUT_USER';
}
| {
type:
| 'RESULTS_NOT_FOUND'
| 'RATE_LIMIT_REACHED'
| 'UNKNOWN_REQUEST_ERROR';
errorMessage: string;
};
interface AppStateContext {
state: AppState;
dispatch: Dispatch<StateAction>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment