Skip to content

Instantly share code, notes, and snippets.

@Jaredk3nt
Last active November 9, 2024 13:08
Show Gist options
  • Save Jaredk3nt/bee978b6e1b73541e864aa7c365bea05 to your computer and use it in GitHub Desktop.
Save Jaredk3nt/bee978b6e1b73541e864aa7c365bea05 to your computer and use it in GitHub Desktop.
React Context Thunk Pattern
const MyContext = createContext();
function reducer(state, action) {
switch (action.type) {
case "COUNT_REQUEST":
return {
...state,
loading: true,
err: undefined
};
case "COUNT_RECIEVE":
return {
...state,
loading: false,
count: action.response
};
case "COUNT_ERROR":
return {
...state,
loading: false,
err: action.err
};
}
}
function useThunk(reducer, initialState) {
// Keep track of mounted state to prevent async calls for updating state and leaking
const mounted = useRef(true);
useEffect(() => () => (mounted.current = false), []);
// Setup underlying useReducer core
const [state, dispatch] = useReducer(reducer, initialState);
// Create a "safe" dispatch for using with async ops
function mountedDispatch(action) {
if (mounted.current) {
dispatch(action);
}
}
// Create thunk function for request dispatch chains
function thunk(options, actions, args = {}, callback) {
mountedDispatch({ type: actions[0], args });
request(options)
.then(response => {
mountedDispatch({ type: actions[1], response, args });
if (callback) callback();
})
.catch(error => {
mountedDispatch({ type: actions[2], error, args });
});
}
// Return in 'tuple' [state, thunk, dispatch]
return [state, thunk, mountedDispatch];
}
function MyProvider(props) {
const thunk = useThunk(reducer, { count: 0, loading: false, err: undefined });
return <MyContext.Provider value={thunk} {...props} />;
}
function useThunkContext() {
const context = useContext(MyContext);
if (!context) {
throw new Error("Must be used within <MyProvider />");
}
return context;
}
function RequestComponent() {
const [state, thunk, dispatch] = useThunkContext();
function getCount() {
thunk({ url }, ["COUNT_REQUEST", "COUNT_RECIEVE", "COUNT_ERROR"]);
}
if (state.err) {
return <p>{state.err}</p>;
}
if (state.loading) {
return <p>Loading...</p>;
}
return <p>{state.count}</p>;
}
function App() {
return (
<MyProvider>
<RequestComponent />
</MyProvider>
);
}
@golinmarq
Copy link

Good pattern. Where the options(url) comes? Also where request(options) came from?

@Jaredk3nt
Copy link
Author

Jaredk3nt commented Feb 23, 2021

Good pattern. Where the options(url) comes? Also where request(options) came from?

request would be whatever http library you want to use (fetch, axios, request, etc...)

@builtbyproxy
Copy link

I'm struggling to understand your requestComponent. It never calls the method getCount?

@Jaredk3nt
Copy link
Author

I'm struggling to understand your requestComponent. It never calls the method getCount?

Yeah it's just a quick example I used to show someone the pattern, its not meant to be run. The request component would have whatever counter logic you would normally get in a counter tutorial.

@builtbyproxy
Copy link

Beauty thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment