52 lines
1.6 KiB
TypeScript
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>];
|
|
};
|