Skip to content

Instantly share code, notes, and snippets.

@SMWARREN
Forked from acorn1010/createGlobalStore.ts
Created February 20, 2023 17:22
Show Gist options
  • Select an option

  • Save SMWARREN/d28a0b97d38506bf810da542fce8dc8e to your computer and use it in GitHub Desktop.

Select an option

Save SMWARREN/d28a0b97d38506bf810da542fce8dc8e to your computer and use it in GitHub Desktop.

Revisions

  1. @acorn1010 acorn1010 revised this gist Jan 23, 2023. No changes.
  2. @acorn1010 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),
    };
    };
  3. @acorn1010 acorn1010 revised this gist Jan 5, 2023. No changes.
  4. @acorn1010 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),
    };
    };