1
0
Fork 0
tsgames/src/common/hooks/useRemote.ts

52 lines
1.6 KiB
TypeScript

import { useEffect, useReducer, useState, type Dispatch, type Reducer, type StateUpdater } from "preact/hooks";
import { loadObject, saveObject } from "@common/storage";
export const useRemoteState = <T>(key: string, initialValue: T) => {
const storedKey = `useRemoteState.${key}`;
const [value, setValue] = useState<T>(initialValue);
useEffect(() => {
loadObject<T>(storedKey, initialValue).then(setValue);
}, [storedKey]);
useEffect(() => {
saveObject(storedKey, value);
}, [storedKey, value]);
return [value, setValue] as [T, Dispatch<StateUpdater<T>>];
};
const hydrate = Symbol('hydrate');
type HydrateAction<T> = { type: typeof hydrate; payload: T };
const isHydrateAction = <T>(action: unknown): action is HydrateAction<T> =>
typeof action === 'object' && action !== null && 'type' in action && action.type === hydrate;
const wrapReducer = <T, A>(reducer: Reducer<T, A>): Reducer<T, A | HydrateAction<T>> =>
(state, action) => {
if (isHydrateAction<T>(action)) {
return action.payload;
}
return reducer(state, action);
};
export const useRemoteReducer = <T, A>(key: string, reducer: Reducer<T, A>, initialValue: T) => {
const storedKey = `useRemoteReducer.${key}`;
const [state, dispatch] = useReducer(wrapReducer(reducer), initialValue);
useEffect(() => {
loadObject<T>(storedKey, initialValue).then((loaded) => {
dispatch({ type: hydrate, payload: loaded });
});
}, [storedKey]);
useEffect(() => {
saveObject(storedKey, state);
}, [storedKey, state]);
return [state, dispatch] as [T, Dispatch<A>];
};