2.0.2 • Published 6 months ago

@weedzcokie/store v2.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

@weedzcokie/store

npm (scoped) npm bundle size (scoped)

Usage

A full example can be found in example.

store.ts:

// Setup store
import { createStore } from "@weedzcokie/store";
type StoreType = {
    counter: number
};

const store = createStore<StoreType>({
    counter: 0
});
type StoreKeys = keyof StoreType;

export const Store = store.Store;

// Use store
store.subscribe("counter", count => {
    console.log(`current count: ${count}`);
});

store.updateStore({
    counter: 1
});

Preact component

import { createStore, PartialStoreListener } from "@weedzcokie/store";

// Create store as in `store.ts`.

export abstract class StoreComponent<P = unknown, S = unknown> extends Component<P, S> {
    listeners: Array<() => void> = [];

    listen<T extends StoreKeys>(key: T, cb: PartialStoreListener<StoreType, T> = () => this.forceUpdate()) {
        this.listeners.push(store.subscribe(key, cb));
    }

    componentWillUnmount() {
        for (const unsubscribe of this.listeners) {
            unsubscribe();
        }
    }
}

Can now be used as:

import { StoreComponent, Store, store } from "./store";
export default class App extends StoreComponent {
    componentDidMount() {
        this.listen("counter");
    }
    // ...
    
    // If you need to use `componentWillUnmount`, do not forget to call `super.componentWillUnmount()`
    componentWillUnmount() {
        super.componentWillUnmount();
        // ...
    }

    // store is available from the exported Store variable.
    render() {
        return (
            <p>Current count: {Store.counter}</p>
            <button onClick={() => store.updateStore({counter: Store.counter + 1})}>Increment count</button>
        );
    }
}

Hooks

Or using hooks:

import { Store, store } from "./store";

// Create store as in `store.ts`.

export function useStore<T extends StoreKeys>(keys: T[]) {
    const [s, set] = useState(false);

    useEffect(() => {
        const update = () => set(current => !current);
        const listeners = keys.map(key => store.subscribe(key, update));

        return () => {
            for (const unsubscribe of listeners) {
                unsubscribe()
            }
        }
    }, []);

    return { data: Store as Pick<StoreType, T> };
}

function App() {
    useStore(["counter"]);

    return (
        <p>Current count: {Store.counter}</p>
        <button onClick={() => store.updateStore({counter: Store.counter + 1})}>Increment count</button>
    );
}

Hooks with "initializers"

./store.ts:

type StoreType = {
    user: null | StoredUser
    favorites: Set<number>
}
type StoreKeys = keyof StoreType;

const store = createStore<StoreType>({
    user: null,
    favorites: new Set(),
});

const initFnLoaders: Map<string, Promise<unknown>> = new Map();
const initFn: Partial<{
    [K in StoreKeys]: () => void
}> = {
    user: async () => {
        // Check if logged in
        const user = localStorage.getItem("user");
        if (user) {
            const res = await fetch("/api/auth");

            if (!res || res.status !== 200) {
                localStorage.removeItem("user");
                store.updateStore({user: null});
                return;
            }

            store.updateStore({user: user})
        }
    },
}

export function useStore<T extends StoreKeys>(keys: T[]) {
  const [_s, set] = useState(false);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const propsToLoad: Set<string> = new Set();
    const update = () => set(current => !current);
    const listeners = keys.map((key) => store.subscribe(key, update));

    for (const key of keys) {
      let promise = initFnLoaders.get(key);
      if (promise) {
        propsToLoad.add(key);
        promise.then(() => {
          propsToLoad.delete(key);
          setLoading(!!propsToLoad.size);
        });
      }

      const fn = initFn[key];
      if (fn) {
        propsToLoad.add(key);
        promise = fn().then(() => {
          initFnLoaders.delete(key);
          propsToLoad.delete(key);
          setLoading(!!propsToLoad.size);
        });

        initFnLoaders.set(key, promise);

        delete initFn[key];
      }
    }

    if (propsToLoad.size === 0) {
      setLoading(false);
    }

    return () => {
      for (const unsubscribe of listeners) {
        unsubscribe();
      }
    };
  }, []);

  return { loading, data: Store as Pick<StoreType, T> };
}

./profile.tsx:

export default ProfilePage() {
    const {loading, data} = useStore(["user"]);
    if (loading || !data.user) {
        return null;
    }

    return <Profile />
}
2.0.2

6 months ago

2.0.1

9 months ago

2.0.0

1 year ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.5

2 years ago

1.1.3

2 years ago

1.1.2

2 years ago

1.0.4

2 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago