Skip to content

Instantly share code, notes, and snippets.

@acorn1010
Last active January 31, 2025 01:01
Show Gist options
  • Select an option

  • Save acorn1010/9f4621d3dfc33052ffd84f6c2a06d4d6 to your computer and use it in GitHub Desktop.

Select an option

Save acorn1010/9f4621d3dfc33052ffd84f6c2a06d4d6 to your computer and use it in GitHub Desktop.

Revisions

  1. acorn1010 revised this gist Oct 11, 2023. 1 changed file with 31 additions and 13 deletions.
    44 changes: 31 additions & 13 deletions createGlobalStore.ts
    Original 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);
    // eslint-disable-next-line react-hooks/rules-of-hooks
    const keySetter = useCallback((value: SetStateAction<State[K]>) => setter(key, value), [key]);
    return [result, keySetter];
    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;
    } 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('');
    }
    }
    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('');
    }
  2. acorn1010 revised this gist Sep 25, 2023. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion createGlobalStore.ts
    Original 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 {[key]: _, ...rest} = prevState;
    const rest = {...prevState};
    delete rest[key];
    return rest as Partial<State>;
    }, true);
    },
  3. acorn1010 revised this gist Mar 28, 2023. 1 changed file with 48 additions and 3 deletions.
    51 changes: 48 additions & 3 deletions createGlobalStore.ts
    Original 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) => {
    const store = create<State>(() => structuredClone(initialState));
    // 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 State; // TODO(acorn1010): Why can't this be Omit<State, K>?
    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(structuredClone(initialState), true),
    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('');
    }
    }
  4. acorn1010 revised this gist Mar 3, 2023. 1 changed file with 23 additions and 8 deletions.
    31 changes: 23 additions & 8 deletions createGlobalStore.ts
    Original file line number Diff line number Diff line change
    @@ -1,17 +1,11 @@
    import {SetStateAction, useCallback} from 'react';
    import create from "zustand";
    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
    *
    @@ -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 OptionalKeys<State>>(key: K) {
    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,
    };
    }
  5. acorn1010 revised this gist Jan 23, 2023. No changes.
  6. acorn1010 revised this gist Jan 15, 2023. 1 changed file with 17 additions and 0 deletions.
    17 changes: 17 additions & 0 deletions createGlobalStore.ts
    Original 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),
    };
    };
  7. acorn1010 revised this gist Jan 5, 2023. No changes.
  8. acorn1010 created this gist Jan 3, 2023.
    75 changes: 75 additions & 0 deletions createGlobalStore.ts
    Original 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),
    };
    };