mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-13 08:38:43 +03:00
interface: add optimistic updating to stores
This commit is contained in:
parent
8ec4cf4200
commit
a8581ed81e
@ -1,24 +1,39 @@
|
||||
import produce, { setAutoFreeze } from 'immer';
|
||||
import produce, { applyPatches, Patch, produceWithPatches, setAutoFreeze, enablePatches } from 'immer';
|
||||
import { compose } from 'lodash/fp';
|
||||
import create, { State, UseStore } from 'zustand';
|
||||
import _ from 'lodash';
|
||||
import create, { UseStore } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
setAutoFreeze(false);
|
||||
enablePatches();
|
||||
|
||||
export const stateSetter = <StateType>(
|
||||
fn: (state: StateType) => void,
|
||||
set
|
||||
export const stateSetter = <T extends {}>(
|
||||
fn: (state: Readonly<T & BaseState<T>>) => void,
|
||||
set: (newState: T & BaseState<T>) => void
|
||||
): void => {
|
||||
set(produce(fn));
|
||||
set(produce(fn) as any);
|
||||
};
|
||||
|
||||
export const optStateSetter = <T extends {}>(
|
||||
fn: (state: T & BaseState<T>) => void,
|
||||
set: (newState: T & BaseState<T>) => void,
|
||||
get: () => T & BaseState<T>
|
||||
): string => {
|
||||
const old = get();
|
||||
const id = _.uniqueId()
|
||||
const [state, ,patches] = produceWithPatches(old, fn) as readonly [(T & BaseState<T>), any, Patch[]];
|
||||
set({ ...state, patches: { ...state.patches, [id]: patches }});
|
||||
return id;
|
||||
};
|
||||
|
||||
|
||||
export const reduceState = <
|
||||
StateType extends BaseState<StateType>,
|
||||
UpdateType
|
||||
S extends {},
|
||||
U
|
||||
>(
|
||||
state: UseStore<StateType>,
|
||||
data: UpdateType,
|
||||
reducers: ((data: UpdateType, state: StateType) => StateType)[]
|
||||
state: UseStore<S & BaseState<S>>,
|
||||
data: U,
|
||||
reducers: ((data: U, state: S & BaseState<S>) => S & BaseState<S>)[]
|
||||
): void => {
|
||||
const reducer = compose(reducers.map(r => sta => r(data, sta)));
|
||||
state.getState().set((state) => {
|
||||
@ -26,6 +41,18 @@ export const reduceState = <
|
||||
});
|
||||
};
|
||||
|
||||
export const optReduceState = <S, U>(
|
||||
state: UseStore<S & BaseState<S>>,
|
||||
data: U,
|
||||
reducers: ((data: U, state: S & BaseState<S>) => BaseState<S> & S)[]
|
||||
): string => {
|
||||
const reducer = compose(reducers.map(r => sta => r(data, sta)));
|
||||
return state.getState().optSet((state) => {
|
||||
reducer(state);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export let stateStorageKeys: string[] = [];
|
||||
|
||||
export const stateStorageKey = (stateName: string) => {
|
||||
@ -40,19 +67,56 @@ export const stateStorageKey = (stateName: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export interface BaseState<StateType> extends State {
|
||||
set: (fn: (state: StateType) => void) => void;
|
||||
export interface BaseState<StateType> {
|
||||
rollback: (id: string) => void;
|
||||
patches: {
|
||||
[id: string]: Patch[];
|
||||
};
|
||||
set: (fn: (state: BaseState<StateType>) => void) => void;
|
||||
addPatch: (id: string, ...patch: Patch[]) => void;
|
||||
removePatch: (id: string) => void;
|
||||
optSet: (fn: (state: BaseState<StateType>) => void) => string;
|
||||
}
|
||||
|
||||
export const createState = <T extends BaseState<T>>(
|
||||
export const createState = <T extends {}>(
|
||||
name: string,
|
||||
properties: { [K in keyof Omit<T, 'set'>]: T[K] },
|
||||
blacklist: string[] = []
|
||||
): UseStore<T> => create(persist((set, get) => ({
|
||||
properties: T,
|
||||
blacklist: (keyof BaseState<T> | keyof T)[] = []
|
||||
): UseStore<T & BaseState<T>> => create<T & BaseState<T>>(persist<T & BaseState<T>>((set, get) => ({
|
||||
set: fn => stateSetter(fn, set),
|
||||
...properties as any
|
||||
optSet: fn => {
|
||||
return optStateSetter(fn, set, get);
|
||||
},
|
||||
patches: {},
|
||||
addPatch: (id: string, ...patch: Patch[]) => {
|
||||
set(({ patches }) => ({ patches: {...patches, [id]: patch }}));
|
||||
},
|
||||
removePatch: (id: string) => {
|
||||
set(({ patches }) => ({ patches: _.omit(patches, id)}));
|
||||
},
|
||||
rollback: (id: string) => {
|
||||
set(state => {
|
||||
const applying = state.patches[id]
|
||||
return {...applyPatches(state, applying), patches: _.omit(state.patches, id) }
|
||||
});
|
||||
},
|
||||
...properties
|
||||
}), {
|
||||
blacklist,
|
||||
name: stateStorageKey(name),
|
||||
version: process.env.LANDSCAPE_SHORTHASH as any
|
||||
}));
|
||||
|
||||
export async function doOptimistically<A, S extends {}>(state: UseStore<S & BaseState<S>>, action: A, call: (a: A) => Promise<any>, reduce: ((a: A, fn: S & BaseState<S>) => S & BaseState<S>)[]) {
|
||||
let num: string | undefined = undefined;
|
||||
try {
|
||||
num = optReduceState(state, action, reduce);
|
||||
await call(action);
|
||||
state.getState().removePatch(num)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if(num) {
|
||||
state.getState().rollback(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user