Last active
January 31, 2025 01:01
-
-
Save acorn1010/9f4621d3dfc33052ffd84f6c2a06d4d6 to your computer and use it in GitHub Desktop.
Revisions
-
acorn1010 revised this gist
Oct 11, 2023 . 1 changed file with 31 additions and 13 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -3,6 +3,23 @@ import {create} from "zustand"; export type EqualityFn<T> = (left: T | null | undefined, right: T | null | undefined) => boolean; export type StoreType<State> = { use<K extends keyof State>( key: K, defaultValue?: State[K], equalityFn?: EqualityFn<State[K]>, ): [State[K], (value: SetStateAction<State[K]>) => void]; useAll: () => State; delete: <K extends keyof State>(key: K) => void; get: <K extends keyof State>(key: K) => State[K]; getAll: () => State; has: <K extends keyof State>(key: K) => boolean; setAll: (state: State) => void; update: (state: Partial<State>) => void; set: <K extends keyof State>(key: K, value: SetStateAction<State[K]>) => void; reset: () => void; }; // eslint-disable-next-line @typescript-eslint/ban-types const isFunction = (fn: unknown): fn is Function => (typeof fn === 'function'); @@ -47,10 +64,12 @@ export const createGlobalStore = <State extends object>(initialState: State) => if (defaultValue !== undefined && !(key in store.getState())) { setter(key, defaultValue); } const result = store((state) => state[key], equalityFn || Object.is); const keySetter = useCallback( (value: SetStateAction<State[K]>) => setter(key, value), [key], ); return [result! as State[K], keySetter]; }, /** Listens on the entire state, causing a re-render when anything in the state changes. */ @@ -141,13 +160,12 @@ function deepClone<T>(obj: T): T { function getRegExpFlags(regExp: RegExp): string { if ((typeof regExp.source as any).flags === 'string') { return (regExp.source as any).flags; } const flags = []; regExp.global && flags.push('g'); regExp.ignoreCase && flags.push('i'); regExp.multiline && flags.push('m'); regExp.sticky && flags.push('y'); regExp.unicode && flags.push('u'); return flags.join(''); } -
acorn1010 revised this gist
Sep 25, 2023 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -59,7 +59,8 @@ export const createGlobalStore = <State extends object>(initialState: State) => /** Deletes a `key` from state, causing a re-render for anything listening. */ delete<K extends keyof State>(key: K) { store.setState(prevState => { const rest = {...prevState}; delete rest[key]; return rest as Partial<State>; }, true); }, -
acorn1010 revised this gist
Mar 28, 2023 . 1 changed file with 48 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -27,7 +27,8 @@ const isFunction = (fn: unknown): fn is Function => (typeof fn === 'function'); * }; */ export const createGlobalStore = <State extends object>(initialState: State) => { // NOTE: Not using structuredClone because browser support only goes about 2 years back. const store = create<State>(() => deepClone(initialState)); const setter = <T extends keyof State>(key: T, value: SetStateAction<State[T]>) => { if (isFunction(value)) { @@ -59,7 +60,7 @@ export const createGlobalStore = <State extends object>(initialState: State) => delete<K extends keyof State>(key: K) { store.setState(prevState => { const {[key]: _, ...rest} = prevState; return rest as Partial<State>; }, true); }, @@ -86,7 +87,7 @@ export const createGlobalStore = <State extends object>(initialState: State) => update: (state: Partial<State>) => store.setState(state, false), /** Resets the entire state back to its initial state when the store was created. */ reset: () => store.setState(deepClone(initialState), true), }; }; @@ -105,3 +106,47 @@ export function createReadonlyStore<T extends ReturnType<typeof createGlobalStor useAll: store.useAll, }; } /** * Deeply copies objects. Borrowed from just-clone, but with some nicer types. * See: https://github.com/angus-c/just/blob/master/packages/collection-clone/index.cjs */ function deepClone<T>(obj: T): T { let result = obj; const type = {}.toString.call(obj).slice(8, -1); if (type === 'Set') { return new Set([...obj as Set<any>].map(value => deepClone(value))) as any; } if (type === 'Map') { return new Map([...obj as Set<any>].map(kv => [deepClone(kv[0]), deepClone(kv[1])])) as any; } if (type === 'Date') { return new Date((obj as Date).getTime()) as any; } if (type === 'RegExp') { return RegExp((obj as RegExp).source as string, getRegExpFlags(obj as RegExp)) as any; } if (type === 'Array' || type === 'Object') { result = Array.isArray(obj) ? [] : {} as any; for (const key in obj) { // include prototype properties result[key] = deepClone(obj[key]); } } // primitives and non-supported objects (e.g. functions) land here return result; } function getRegExpFlags(regExp: RegExp): string { if ((typeof regExp.source as any).flags === 'string') { return (regExp.source as any).flags; } else { const flags = []; regExp.global && flags.push('g'); regExp.ignoreCase && flags.push('i'); regExp.multiline && flags.push('m'); regExp.sticky && flags.push('y'); regExp.unicode && flags.push('u'); return flags.join(''); } } -
acorn1010 revised this gist
Mar 3, 2023 . 1 changed file with 23 additions and 8 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,17 +1,11 @@ import {SetStateAction, useCallback} from 'react'; import {create} from "zustand"; export type EqualityFn<T> = (left: T | null | undefined, right: T | null | undefined) => boolean; // eslint-disable-next-line @typescript-eslint/ban-types const isFunction = (fn: unknown): fn is Function => (typeof fn === 'function'); /** * Create a global state * @@ -62,7 +56,7 @@ export const createGlobalStore = <State extends object>(initialState: State) => useAll: () => store(state => state), /** Deletes a `key` from state, causing a re-render for anything listening. */ delete<K extends keyof State>(key: K) { store.setState(prevState => { const {[key]: _, ...rest} = prevState; return rest as State; // TODO(acorn1010): Why can't this be Omit<State, K>? @@ -77,6 +71,11 @@ export const createGlobalStore = <State extends object>(initialState: State) => /** Retrieves the entire state. Does _not_ listen on state changes (meaning no re-renders). */ getAll: () => store.getState(), /** Returns `true` if `key` is in the state. */ has<K extends keyof State>(key: K) { return key in store.getState(); }, /** Sets a `key`, triggering a re-render for all listeners. */ set: setter, @@ -90,3 +89,19 @@ export const createGlobalStore = <State extends object>(initialState: State) => reset: () => store.setState(structuredClone(initialState), true), }; }; /** * Returns a wrapped `store` that can't be modified. Useful when you want to * control who is able to write to a store. */ export function createReadonlyStore<T extends ReturnType<typeof createGlobalStore>>( store: T) { type State = ReturnType<T['getAll']>; return { get: store.get, getAll: store.getAll, use: <K extends keyof State>(key: K, equalityFn?: EqualityFn<State[K]>) => (store.use as any)(key, undefined, equalityFn)[0] as State[K] | undefined | null, useAll: store.useAll, }; } -
acorn1010 revised this gist
Jan 23, 2023 . No changes.There are no files selected for viewing
-
acorn1010 revised this gist
Jan 15, 2023 . 1 changed file with 17 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -43,6 +43,7 @@ export const createGlobalStore = <State extends object>(initialState: State) => } }; return { /** Works like React.useState. "Registers" the component as a listener on that key. */ use<K extends keyof State>( key: K, defaultValue?: State[K], @@ -56,20 +57,36 @@ export const createGlobalStore = <State extends object>(initialState: State) => const keySetter = useCallback((value: SetStateAction<State[K]>) => setter(key, value), [key]); return [result, keySetter]; }, /** Listens on the entire state, causing a re-render when anything in the state changes. */ useAll: () => store(state => state), /** Deletes a `key` from state, causing a re-render for anything listening. */ delete<K extends OptionalKeys<State>>(key: K) { store.setState(prevState => { const {[key]: _, ...rest} = prevState; return rest as State; // TODO(acorn1010): Why can't this be Omit<State, K>? }, true); }, /** Retrieves the current `key` value. Does _not_ listen on state changes (meaning no re-renders). */ get<K extends keyof State>(key: K) { return store.getState()[key]; }, /** Retrieves the entire state. Does _not_ listen on state changes (meaning no re-renders). */ getAll: () => store.getState(), /** Sets a `key`, triggering a re-render for all listeners. */ set: setter, /** Sets the entire state, removing any keys that aren't present in `state`. */ setAll: (state: State) => store.setState(state, true), /** Updates the keys in `state`, leaving any keys / values not in `state` unchanged. */ update: (state: Partial<State>) => store.setState(state, false), /** Resets the entire state back to its initial state when the store was created. */ reset: () => store.setState(structuredClone(initialState), true), }; }; -
acorn1010 revised this gist
Jan 5, 2023 . No changes.There are no files selected for viewing
-
acorn1010 created this gist
Jan 3, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,75 @@ import {SetStateAction, useCallback} from 'react'; import create from "zustand"; export type EqualityFn<T> = (left: T | null | undefined, right: T | null | undefined) => boolean; // eslint-disable-next-line @typescript-eslint/ban-types const isFunction = (fn: unknown): fn is Function => (typeof fn === 'function'); /** Given a type `T`, returns the keys that are Optional. */ type OptionalKeys<T> = string extends keyof T ? string : { [K in keyof T]-?: Record<string, unknown> extends Pick<T, K> ? K : never }[keyof T]; /** * Create a global state * * It returns a set of functions * - `use`: Works like React.useState. "Registers" the component as a listener on that key * - `get`: retrieves a key without a re-render * - `set`: sets a key. Causes re-renders on any listeners * - `getAll`: retrieves the entire state (all keys) as an object without a re-render * - `reset`: resets the state back to its initial value * * @example * import { createStore } from 'create-store'; * * const store = createStore({ count: 0 }); * * const Component = () => { * const [count, setCount] = store.use('count'); * ... * }; */ export const createGlobalStore = <State extends object>(initialState: State) => { const store = create<State>(() => structuredClone(initialState)); const setter = <T extends keyof State>(key: T, value: SetStateAction<State[T]>) => { if (isFunction(value)) { store.setState(prevValue => ({[key]: value(prevValue[key])} as unknown as Partial<State>)); } else { store.setState({[key]: value} as unknown as Partial<State>); } }; return { use<K extends keyof State>( key: K, defaultValue?: State[K], equalityFn?: EqualityFn<State[K]>): [State[K], (value: SetStateAction<State[K]>) => void] { // If state isn't defined for a given defaultValue, set it. if (defaultValue !== undefined && !(key in store.getState())) { setter(key, defaultValue); } const result = store(state => state[key], equalityFn); // eslint-disable-next-line react-hooks/rules-of-hooks const keySetter = useCallback((value: SetStateAction<State[K]>) => setter(key, value), [key]); return [result, keySetter]; }, useAll: () => store(state => state), delete<K extends OptionalKeys<State>>(key: K) { store.setState(prevState => { const {[key]: _, ...rest} = prevState; return rest as State; // TODO(acorn1010): Why can't this be Omit<State, K>? }, true); }, get<K extends keyof State>(key: K) { return store.getState()[key]; }, getAll: () => store.getState(), set: setter, setAll: (state: State) => store.setState(state, true), update: (state: Partial<State>) => store.setState(state, false), reset: () => store.setState(structuredClone(initialState), true), }; };