Last active
November 9, 2024 13:08
-
-
Save Jaredk3nt/bee978b6e1b73541e864aa7c365bea05 to your computer and use it in GitHub Desktop.
React Context Thunk Pattern
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} |
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...)
I'm struggling to understand your requestComponent. It never calls the method getCount
?
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.
Beauty thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good pattern. Where the options(url) comes? Also where request(options) came from?