Skip to content

Instantly share code, notes, and snippets.

@Retsam
Last active January 31, 2021 18:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Retsam/1ec0204c7abfb42de8f5db7eb20c2957 to your computer and use it in GitHub Desktop.
Save Retsam/1ec0204c7abfb42de8f5db7eb20c2957 to your computer and use it in GitHub Desktop.
Redux-like useReducer pattern
import { Dispatch, useReducer, useEffect } from "react";
import { produce } from "immer";
//=======
// State
//=======
export type ExampleState = {
name: string;
level: number;
saved: false;
};
const initialState: ExampleState = {
name: "Squirtle",
level: 5,
saved: false,
};
//=========
// Actions
//=========
type ExampleSyncAction =
| { type: "levelUp" }
| { type: "evolve" }
| { type: "tradeSuccess" };
type ExampleAsyncAction = { type: "trade" } | { type: "save" };
declare const doTrade: () => Promise<void>;
export type ExampleAction = ExampleSyncAction | ExampleAsyncAction;
function assertNever(_action: never, msg: string): never {
throw new Error(msg);
}
//=========
// Reducer
//=========
const exampleReducer = produce(
(state: ExampleState, action: ExampleSyncAction) => {
if (action.type === "levelUp") {
state.level += 1;
} else if (action.type === "evolve") {
state.name = state.name === "Squirtle" ? "Wartortle" : "Blastoise";
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (action.type === "tradeSuccess") {
state.name = "Charmander";
} else {
assertNever(action, `Unhandled action ${(action as any).type}`);
}
},
);
//===========
// Selectors
//===========
export function isFullyEvolved(state: ExampleState) {
return state.name === "Blastoise";
}
//======
// Hook
//======
export type ExampleDispatcher = Dispatch<ExampleAction>;
export function useExampleState(): [ExampleState, ExampleDispatcher] {
const [state, syncDispatch] = useReducer(exampleReducer, initialState);
/* Wrapper around syncDispatch, which performs async actions */
const dispatch = (action: ExampleAction) => {
if (action.type === "trade") {
doTrade().then(() => {
syncDispatch({ type: "tradeSuccess" });
});
} else if (action.type === "save") {
// ...
} else {
syncDispatch(action);
}
};
useEffect(() => {
// "Reaction" to initial load
}, []);
const { level, name } = state;
useEffect(() => {
// "Reaction" to level change
dispatch({ type: "save" });
}, [level, name]);
return [state, dispatch];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment