Next.js Example

Craft your next amazing library using

Harness the full potential of React 18 Server Components!

A tiny yet powerful store for modern react libraries.

NPM Bundle Size

Kosha

Extract as Object

const { p2, setP2 } = myStore(({ p2, setP2 }) => ({ p2, setP2 }))

p1: | renderCount is 1

p2:0.000000 | renderCount is 1


Extract as Array

const [p2, setP2] = myStore(({ p2, setP2 }) => [p2, setP2])

p1: | renderCount is 1

p2:0.000000 | renderCount is 1


Extract individually

const p2 = myStore(({ p2 }) => p2)

p1: | renderCount is 1

p2:0.000000 | renderCount is 1

Zustand

Extract as Object

const { p2, setP2 } = myStore(({ p2, setP2 }) => ({ p2, setP2 }))

p1: | renderCount is 1

p2:0.000000 | renderCount is 1


Extract as Array

const [p2, setP2] = myStore(({ p2, setP2 }) => [p2, setP2])

p1: | renderCount is 1

p2:0.000000 | renderCount is 1


Extract individually

const p2 = myStore(({ p2 }) => p2)

p1: | renderCount is 1

p2:0.000000 | renderCount is 1

ShowHide Code
import { useRef } from "react";
import { getStore, ICompareStore, ICompareStoreActions } from "./store";
import { ErrorBoundary } from "react-error-boundary";
import styles from "./compare.module.scss";

interface StoreTypeProps {
  type: "zustand" | "kosha";
}

type ComponentProps = ICompareStore & ICompareStoreActions;

const P1 = ({ p1, setP1 }: Pick<ComponentProps, "p1" | "setP1">) => {
  const renderCountRef = useRef(0);
  renderCountRef.current++;
  return (
    <div>
      <p>
        p1:
        <button onClick={() => setP1(crypto.randomUUID())}>Update</button>
        {p1.slice(25)} | renderCount is {renderCountRef.current}
      </p>
    </div>
  );
};

const P2 = ({ p2, setP2 }: Pick<ComponentProps, "p2" | "setP2">) => {
  const renderCountRef = useRef(0);
  renderCountRef.current++;
  return (
    <div>
      <p>
        p2:
        <button onClick={() => setP2(Math.random())}>Update</button>
        {p2.toFixed(6)} | renderCount is {renderCountRef.current}
      </p>
    </div>
  );
};

const P1ExtractAsObj = ({ type }: StoreTypeProps) => {
  const { p1, setP1 } = getStore(type)(({ p1, setP1 }) => ({ p1, setP1 }));
  return <P1 {...{ p1, setP1 }} />;
};

const P1ExtractAsArray = ({ type }: StoreTypeProps) => {
  const [p1, setP1] = getStore(type)(({ p1, setP1 }) => [p1, setP1]);
  return <P1 {...{ p1, setP1 }} />;
};

const P1ExtractIndividually = ({ type }: StoreTypeProps) => {
  const p1 = getStore(type)(({ p1 }) => p1);
  const setP1 = getStore(type)(({ setP1 }) => setP1);
  return <P1 {...{ p1, setP1 }} />;
};

const P2ExtractAsObj = ({ type }: StoreTypeProps) => {
  const { p2, setP2 } = getStore(type)(({ p2, setP2 }) => ({ p2, setP2 }));
  return <P2 {...{ p2, setP2 }} />;
};

const P2ExtractAsArray = ({ type }: StoreTypeProps) => {
  const [p2, setP2] = getStore(type)(({ p2, setP2 }) => [p2, setP2]);
  return <P2 {...{ p2, setP2 }} />;
};

const P2ExtractIndividually = ({ type }: StoreTypeProps) => {
  const p2 = getStore(type)(({ p2 }) => p2);
  const setP2 = getStore(type)(({ setP2 }) => setP2);
  return <P2 {...{ p2, setP2 }} />;
};

export const Compare = () => (
  <div className={styles.compare}>
    <div className={styles.compare__store}>
      <h2>Kosha</h2>
      <h4>Extract as Object</h4>
      <code>{`const { p2, setP2 } = myStore(({ p2, setP2 }) => ({ p2, setP2 }))`}</code>
      <P1ExtractAsObj type="kosha" />
      <P2ExtractAsObj type="kosha" />
      <hr />
      <h4>Extract as Array</h4>
      <code>{`const [p2, setP2] = myStore(({ p2, setP2 }) => [p2, setP2])`}</code>
      <P1ExtractAsArray type="kosha" />
      <P2ExtractAsArray type="kosha" />
      <hr />
      <h4>Extract individually</h4>
      <code>{`const p2 = myStore(({ p2 }) => p2)`}</code>
      <P1ExtractIndividually type="kosha" />
      <P2ExtractIndividually type="kosha" />
    </div>
    <div className={styles.compare__store}>
      <h2>Zustand</h2>
      <h4>Extract as Object</h4>
      <code>{`const { p2, setP2 } = myStore(({ p2, setP2 }) => ({ p2, setP2 }))`}</code>
      <ErrorBoundary
        fallback={
          <div>
            <p>⚠️Something went wrong</p>
          </div>
        }>
        <P1ExtractAsObj type="zustand" />
      </ErrorBoundary>
      <ErrorBoundary
        fallback={
          <div>
            <p>⚠️Something went wrong</p>
          </div>
        }>
        <P2ExtractAsObj type="zustand" />
      </ErrorBoundary>
      <hr />
      <h4>Extract as Array</h4>
      <code>{`const [p2, setP2] = myStore(({ p2, setP2 }) => [p2, setP2])`}</code>
      <ErrorBoundary
        fallback={
          <div>
            <p>⚠️Something went wrong</p>
          </div>
        }>
        <P1ExtractAsArray type="zustand" />
      </ErrorBoundary>
      <ErrorBoundary
        fallback={
          <div>
            <p>⚠️Something went wrong</p>
          </div>
        }>
        <P2ExtractAsArray type="zustand" />
      </ErrorBoundary>
      <hr />
      <h4>Extract individually</h4>
      <code>{`const p2 = myStore(({ p2 }) => p2)`}</code>
      <P1ExtractIndividually type="zustand" />
      <P2ExtractIndividually type="zustand" />
    </div>
  </div>
);

Example using persist middleware

Count: 0
Local Count: 0
ShowHide Code
import { create } from "kosha";
import { persist } from "kosha/middleware";
import styles from "../demo.module.scss";

interface CounterStore {
  count: number;
  localCount: number;
  setCount: (count: number) => void;
  setLocalCount: (count: number) => void;
}

const usePersistedKosha = create(
  persist<CounterStore>({ key: "test-kosha", partialize: state => ({ count: state.count }) })(
    set => ({
      count: 0,
      localCount: 0,
      setCount: (count: number) => set({ count }),
      setLocalCount: localCount => set({ localCount }),
    }),
  ),
);

export const PersistedCounter = () => {
  const { count, localCount, setCount, setLocalCount } = usePersistedKosha();
  return (
    <div className={styles.preview}>
      <h2>Example using persist middleware</h2>
      <div>
        Count: {count}
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </div>
      <div>
        Local Count: {localCount}
        <button onClick={() => setLocalCount(localCount + 1)}>Increment</button>
      </div>
    </div>
  );
};

Example using immer middleware

Count: 0
ShowHide Code
import { create } from "kosha";
import { immer } from "kosha/middleware";
import styles from "../demo.module.scss";

interface CounterStore {
  count: number;
  setCount: (count: number) => void;
}

const useKoshaWithImmer = create(
  immer<CounterStore>(set => ({
    count: 0,
    setCount: (count: number) =>
      set(state => {
        state.count = count;
      }),
  })),
);

export const CounterWithImmer = () => {
  const { count, setCount } = useKoshaWithImmer();
  return (
    <div className={styles.preview}>
      <h2>Example using immer middleware</h2>
      <div>
        Count: {count}
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </div>
    </div>
  );
};

Example using slices to create the store.

Count: 0
Theme: light
ShowHide Code
import { create, SliceCreator } from "kosha";
import styles from "../demo.module.scss";

interface CounterSlice {
  count: number;
  setCount: (count: number) => void;
}

interface ThemeSlice {
  theme: string;
  setTheme: (theme: string) => void;
}

type StoreType = CounterSlice & ThemeSlice;

const counterSlice: SliceCreator<StoreType, CounterSlice> = set => ({
  count: 0,
  setCount: (count: number) => set({ count }),
});

const createThemeSlice: SliceCreator<StoreType, ThemeSlice> = set => ({
  theme: "light",
  setTheme: (theme: string) => set({ theme }),
});

const useKosha = create<StoreType>((...a) => ({
  ...createThemeSlice(...a),
  ...counterSlice(...a),
}));

export const SlicingTheStore = () => {
  const { count, setCount, theme, setTheme } = useKosha();
  return (
    <div className={styles.preview}>
      <h2>Example using slices to create the store.</h2>
      <div>
        Count: {count}
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </div>
      <div>
        Theme: {theme}
        <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle Theme</button>
      </div>
    </div>
  );
};

Basic Example: Synced counters

All components below share the same state without any wrapper.

Counter Controller 1

Counter Display 1

0

Counter Controller 2

Counter Display 2

0
ShowHide Code
import { create } from "kosha";

interface CounterStore {
  count: number;
  setCount: (count: number) => void;
}

const useKosha = create<CounterStore>(set => ({
  count: 0,
  setCount: (count: number) => set(state => ({ ...state, count })),
}));

/** Counter Controller */
export const CounterController = () => {
  const [count, setCount] = useKosha(({ count, setCount }) => [count, setCount]);
  return <input type="number" value={count} onChange={e => setCount(Number(e.target.value))} />;
};

/** Counter Display  */
export const CounterDisplay = () => {
  const { count } = useKosha();
  return <div>{count}</div>;
};

Example with Selectors

My name is John

Updates only when name changes. renderCount = 1

Counter With Selectors

Rerender is triggered by RGS only when count changes.

Render Count: 1

Count: 0

Counter Without Selectors

Rerender is triggered every time the state changes.

Render Count: 1

Count: 0

UserData

renderCount = 1 | Name: John | Age: 30

ShowHide Code
import { create, StateSetter } from "kosha";

interface MyKosha {
  count: number;
  name: string;
  user: {
    name: string;
    age: number;
  };
  setCount: (count: number) => void;
  set: StateSetter<MyKosha>;
}

export const useMyKosha = create<MyKosha>(set => ({
  count: 0,
  name: "John",
  user: {
    name: "John",
    age: 30,
  },
  setCount: (count: number) => set(state => ({ ...state, count })),
  set,
}));

Docs ->

Check out the official documentation for more information.

More Examples ->

Check out more examples on the official GitHub Repo. Feel free to suggest additional examples in the discussions section.

Star this repo ->

Star this repo for your new library! This also motivates us and helps us understand that community is interested in this work.
Fork Me on GitHub