Skip to content

Instantly share code, notes, and snippets.

@dr-skot
Last active May 10, 2022 00:01
Show Gist options
  • Save dr-skot/05be1e4aebe6195e90e8efd68e23a1d1 to your computer and use it in GitHub Desktop.
Save dr-skot/05be1e4aebe6195e90e8efd68e23a1d1 to your computer and use it in GitHub Desktop.
React hook like useReducer but can update state asynchronously
// useAsyncReducer works like useReducer, except the reducer has access to the dispatch function
// so it can change state after async operations complete
// example usage:
import useAsyncReducer from './use-async-reducer';
function reducer(state, dispatch, action, args) {
switch (action) {
case 'fetch':
return [
state,
fetch(args.url).then((fetched) => { dispatch('processFetch', { fetched }); return fetched }),
];
case 'processFetch':
return [{ ...state, ...processFetch(args.fetched) }];
default:
return [state];
}
}
function FetchingComponent({ url, initialState }) {
const [state, dispatch] = useAsyncReducer(initialState);
useEffect(() => {
dispatch('fetch', url);
}, [url]);
return <AmazingDataView data={state}/>;
}
// useAsyncReducer works like useReducer, except the reducer has access to the dispatch function
// so it can change state after async operations complete
import { useCallback, useState } from 'react';
// dispatch returns the promise returned by the reducer, if any (otherwise returns Promise.resolve())
type AsyncDispatcher<Action> = (action: Action, ...args: any[]) => Promise<any>;
type AsyncReducer<State, Action> = (
state: State,
redispatch: AsyncDispatcher<Action>, // redispatch to change state after async completes
action: Action,
...args: any[] // use as many arguments as you like
) => [State, Promise<any>?]; // if the reducer return a promise, it will passed on by dispatch()
function useAsyncReducer<State, Action>(
reducer: AsyncReducer<State, Action>,
initialState: State
) {
const [state, setState] = useState(initialState);
const dispatch: AsyncDispatcher<Action> = useCallback(
(action, ...args) =>
new Promise((resolve, reject) => {
setState((prevState) => {
const [nextState, _promise] = reducer(
prevState,
dispatch,
action,
...args
);
(_promise || Promise.resolve()).then(resolve).catch(reject);
return nextState;
});
}),
[reducer]
);
return [state, dispatch];
}
export default useAsyncReducer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment