import React from "react"; export enum FetchStatus { Fetching, FetchFailed, Fetched, } export type CacheEntry = { status: FetchStatus; data?: T; }; export type State = { entries: { [key: string]: CacheEntry; }; url: string; }; export enum ActionType { Update, Clear, } export type Update = { type: ActionType.Update; url: string; key: string | number; status: FetchStatus; data?: T; }; export type Clear = { type: ActionType.Clear; url: string; }; export type Action = Update | Clear; export type Dispatch = (action: Action) => void; type Reducer = (state: State, action: Action) => State; type Reconciler = ( entry: T | undefined, update: U | undefined ) => T | undefined; function defaultReconciler(entry: T | undefined, update: T | undefined) { if (entry) { if (update) { return { ...entry, ...update, }; } else { return entry; } } else { return update; } } function defaultReducer(state: State, action: Action) { return reducer(state, action, defaultReconciler); } export function useReducer(url: string) { return React.useReducer>(defaultReducer, { url, entries: {} }); } export function useCustomReducer( url: string, reconciler: Reconciler ) { const customReducer = React.useMemo(() => { return (state: State, action: Action) => { return reducer(state, action, reconciler); }; }, [reconciler]); return React.useReducer>(customReducer, { url, entries: {} }); } export function reducer( state: State, action: Action, reconciler: Reconciler ): State { if (action.type === ActionType.Clear) { return { url: action.url, entries: {} }; } else if (action.url !== state.url) { return state; } switch (action.type) { case ActionType.Update: { const key = action.key; const entry = state.entries[key]; const entries = { ...state.entries, [key]: { ...entry, status: action.status, data: reconciler(entry?.data, action.data), }, }; return { ...state, entries }; } } }