Async actions with useReducer in React
What about async actions?
React's dispatch
function only knows how to synchronously dispatch a plain action object.
What if we want to dispatch a function with some async logic inside?
// ❌ You can't dispatch a function
dispatch(someAsyncAction()).then(...);
// ✅ You can only dispatch an object
dispatch({ type: 'FETCH_FINISHED', data });
Creating a custom hook
We are going to create a custom React useReducer
hook that knows how to handle functions.
It will allow us to dispatch async actions along with regular actions.
import { useReducer, useCallback } from 'react';
function useReducerWithThunk(reducer, initialState) {
const [state, dispatch] = useReducer(reducer, initialState);
function customDispatch(action) {
if (typeof action === 'function') {
return action(customDispatch);
} else {
dispatch(action);
}
};
// Memoize so you can include it in the dependency array without causing infinite loops
// eslint-disable-next-line react-hooks/exhaustive-deps
const stableDispatch = useCallback(customDispatch, [dispatch]);
return [state, stableDispatch];
}
export { useReducerWithThunk };
Similar to redux-thunk, if the dispatched action is actually a function, the hook calls that function and passes dispatch
as an argument. Otherwise, it's treated as a regular object action.
We use useCallback
to make our custom dispatch function stable (similarly to the regular dispatch function). It won't change on re-renders (important if you pass it to useEffect
dependencies list).
Using the hook
Now you can write thunk action creators that return a function instead of an action object:
// Thunk function
function fetchUserAction() {
return async function fetchUserThunk(dispatch) {
const user = await fetchUser();
dispatch({ type: 'FETCH_SUCCESS', payload: user })
}
}
Then you can use that hook like this:
import { useReducerWithThunk } from './useReducerWithThunk';
function User() {
const [state, dispatch] = useReducerWithThunk(reducer, '');
function handleClick() {
dispatch(fetchUserAction());
}
return (
<>
<button onClick={handleClick}>Fetch user</button>
<p>User: {state}</p>
</>
);
}
Source code is available on codesandbox.