Hummbitv1.x

Public API Matrix

hummbit

  • getState(): Readonly<RootState>
  • setState(input: RootState | NextStateUpdater<RootState>): Promise<void>
  • mergeState(input: Patch<RootState> | Updater<RootState>): Promise<void>
  • configureGlobalStore(config: GlobalStoreConfig): void
  • selector<T>(selectorFn: (state: Readonly<RootState>) => T): T
  • initStore(config, options?): InitializedStore<...>

hummbit/react

  • getState(): Readonly<RootState>
  • setState(input: RootState | NextStateUpdater): Promise<void>
  • mergeState(input: Patch | MergeUpdater): Promise<void>
  • useSelector<T>(selectorFn, equalityFn?): T
  • configureGlobalStore(config): void
  • initStore(config, options?): InitializedReactStore<...>

API Reference


hummbit.getState

Reads a one-time immutable snapshot from the global singleton store.

Example 1: Basic read

import { getState } from "hummbit";

const snapshot = getState();
console.log(snapshot);

Example 2: Read nested field safely

import { getState } from "hummbit";

const userName = getState().user?.name ?? "anonymous";

Example 3: Event handler read

import { getState } from "hummbit";

function onCheckoutClick() {
  const cart = getState().cart;
  submitCheckout(cart);
}

Example 4: Read after awaited update

import { setState, getState } from "hummbit";

await setState((prev) => ({ ...prev, ready: true }));
console.log(getState().ready); // true

Example 5: Anti-pattern vs correct

import { getState } from "hummbit";

// Anti-pattern: assume this value stays fresh forever
const cached = getState().counter;

// Correct: call getState() each time you need fresh data
const fresh = getState().counter;

hummbit.setState

Replaces global root state. Accepts object or updater. Returns Promise<void>.

Example 1: Direct replace object

import { setState } from "hummbit";

await setState({ user: { id: "u1" }, ready: true });

Example 2: Replace using updater

import { setState } from "hummbit";

await setState((prev) => ({ ...prev, ready: !prev.ready }));

Example 3: Async updater

import { setState } from "hummbit";

await setState(async (prev) => {
  const profile = await loadProfile();
  return { ...prev, profile };
});

Example 4: Ordered updates

import { setState } from "hummbit";

const p1 = setState((s) => ({ ...s, step: 1 }));
const p2 = setState((s) => ({ ...s, step: 2 }));
await Promise.all([p1, p2]); // applied in call order

Example 5: Anti-pattern vs correct

import { setState } from "hummbit";

// Anti-pattern: forget unchanged fields
await setState({ user: { id: "u2" } } as any);

// Correct: preserve root explicitly
await setState((prev) => ({ ...prev, user: { ...prev.user, id: "u2" } }));

hummbit.mergeState

Applies shallow root patch. Returns Promise<void>.

Example 1: Root patch

import { mergeState } from "hummbit";

await mergeState({ loading: true });

Example 2: Updater patch

import { mergeState } from "hummbit";

await mergeState((prev) => ({ counter: prev.counter + 1 }));

Example 3: Async patch

import { mergeState } from "hummbit";

await mergeState(async () => {
  const token = await refreshToken();
  return { token };
});

Example 4: Real-world status machine

import { mergeState } from "hummbit";

await mergeState({ requestStatus: "pending" });
await mergeState({ requestStatus: "done" });

Example 5: Anti-pattern vs correct deep update

import { mergeState } from "hummbit";

// Anti-pattern: overwrites full user object
await mergeState({ user: { age: 31 } as any });

// Correct:
await mergeState((prev) => ({ user: { ...prev.user, age: 31 } }));

hummbit.configureGlobalStore

Configures global singleton DevTools/middleware behavior.

Example 1: Disable DevTools

import { configureGlobalStore } from "hummbit";

configureGlobalStore({ devtools: false });

Example 2: Force-enable with custom name

import { configureGlobalStore } from "hummbit";

configureGlobalStore({ devtools: { enabled: true, name: "app-global" } });

Example 3: Add global middleware

import { configureGlobalStore, type Middleware } from "hummbit";

type Root = { count: number };
const logger: Middleware<Root> = (ctx) => ({
  afterUpdate: () => console.log("count", ctx.getState().count),
});
configureGlobalStore({ middleware: [logger] });

Example 4: Startup initialization

import { configureGlobalStore } from "hummbit";

export function bootstrap() {
  configureGlobalStore({ devtools: true });
  // call before first setState/mergeState
}

Example 5: Anti-pattern vs correct

import { configureGlobalStore } from "hummbit";

// Anti-pattern: configure late during runtime
setTimeout(() => configureGlobalStore({ devtools: false }), 5000);

// Correct: configure once at startup
configureGlobalStore({ devtools: false });

hummbit.selector

One-off selector read on global state (non-reactive).

Example 1: Basic read

import { selector } from "hummbit";

const isAuth = selector((s) => Boolean(s.session?.token));

Example 2: Derived value

import { selector } from "hummbit";

const total = selector((s) => s.items.reduce((acc, i) => acc + i.price, 0));

Example 3: Optional chaining

import { selector } from "hummbit";

const city = selector((s) => s.user?.address?.city ?? "unknown");

Example 4: Guard before side effect

import { selector } from "hummbit";

if (selector((s) => s.flags?.canUpload)) {
  startUpload();
}

Example 5: Anti-pattern vs correct

import { selector } from "hummbit";

// Anti-pattern: treat selector as subscription
const value = selector((s) => s.counter);

// Correct: call again after updates, or in React use useSelector
const next = selector((s) => s.counter);

hummbit.initStore

Creates isolated typed store instance with actions, selectors, select.

Example 1: Minimal typed store

import { initStore } from "hummbit";

const store = initStore({
  initialState: { count: 0 },
  actions: ({ actionCreator, setState }) => ({
    inc: actionCreator("inc", () =>
      setState((s) => ({ ...s, count: s.count + 1 })),
    ),
  }),
  selectors: { count: (s) => s.count },
});

Example 2: Using select(...)

const count = store.select(store.selectors.count);

Example 3: freeze: false

import { initStore } from "hummbit";

const store = initStore({
  initialState: { debug: true },
  freeze: false,
  actions: () => ({}),
  selectors: { debug: (s) => s.debug },
});

Example 4: DevTools options (second initStore argument)

import { initStore } from "hummbit";

const store = initStore(
  {
    initialState: { q: 0 },
    devtools: { name: "query-store" },
    actions: () => ({}),
    selectors: { q: (s) => s.q },
  },
  { devtools: { hideSetState: true } },
);

Important distinction:

  • config.devtools (first argument) controls whether DevTools integration is enabled.
  • options.devtools (second argument) controls fine-grained logging/name behavior.
  • They are separate layers and work together.

Full second-argument shape:

type InitStoreOptions = {
  devtools?: {
    name?: string;
    hideSetState?: boolean;
    hideMergeState?: boolean;
  };
};

Meaning:

  • options.devtools.name - store instance name shown in Redux DevTools.
    This has higher priority than config.devtools.name.
  • options.devtools.hideSetState - hides setState entries in DevTools log (does not affect store behavior).
  • options.devtools.hideMergeState - hides mergeState entries in DevTools log (does not affect store behavior).

Example 5: Anti-pattern vs correct

// Anti-pattern: giant global module augmentation for everything
// Correct: prefer instance-local typing with initStore

hummbit/react.getState

Same semantics as hummbit.getState, but typed through public augmentable RootState.

Example 1: Basic read

import { getState } from "hummbit/react";

const st = getState();

Example 2: One-off UI command read

import { getState } from "hummbit/react";

function onOpenModal() {
  if (getState().permissions?.canOpen) openModal();
}

Example 3: Effect helper

import { getState } from "hummbit/react";

track("page_open", { locale: getState().locale });

Example 4: Await then read

import { setState, getState } from "hummbit/react";

await setState((s) => ({ ...s, hydrated: true }));
console.log(getState().hydrated);

Example 5: Anti-pattern vs correct

// Anti-pattern: use getState() to drive render continuously
// Correct: for render data use useSelector(...)

hummbit/react.setState

Global root replacement from React entrypoint.

Example 1: Toggle boolean

import { setState } from "hummbit/react";

await setState((s) => ({ ...s, open: !s.open }));

Example 2: Async fetch integration

import { setState } from "hummbit/react";

await setState(async (s) => ({ ...s, profile: await fetchProfile() }));

Example 3: Loading lifecycle

import { setState } from "hummbit/react";

await setState((s) => ({ ...s, loading: true }));
await setState((s) => ({ ...s, loading: false }));

Example 4: Event callback update

import { setState } from "hummbit/react";

const onDismiss = () => setState((s) => ({ ...s, toast: null }));

Example 5: Anti-pattern vs correct

// Anti-pattern: mutate old state object then return it
// Correct: always return a new root object

hummbit/react.mergeState

Global shallow root merge from React entrypoint.

Example 1: Set loading flag

import { mergeState } from "hummbit/react";

await mergeState({ loading: true });

Example 2: Updater patch

import { mergeState } from "hummbit/react";

await mergeState((s) => ({ counter: s.counter + 1 }));

Example 3: Async patch

import { mergeState } from "hummbit/react";

await mergeState(async () => ({ token: await refreshToken() }));

Example 4: Form wizard

import { mergeState } from "hummbit/react";

await mergeState({ step: 2 });

Example 5: Anti-pattern vs correct

// Anti-pattern: expect deep merge of nested object
// Correct: spread nested fields explicitly

hummbit/react.useSelector

Subscribes React component to global singleton state slices.

Example 1: Basic usage

import { useSelector } from "hummbit/react";

function Counter() {
  const value = useSelector((s) => s.counter);
  return <span>{value}</span>;
}

Example 2: Custom equality

import { useSelector } from "hummbit/react";

const id = useSelector(
  (s) => s.user.id,
  (a, b) => a === b,
);

Example 3: Derived selector

import { useSelector } from "hummbit/react";

const total = useSelector((s) => s.items.reduce((acc, i) => acc + i.qty, 0));

Example 4: Split selectors for perf

const name = useSelector((s) => s.user.name);
const age = useSelector((s) => s.user.age);

Example 5: Anti-pattern vs correct

// Anti-pattern: create unstable object without equality
const userView = useSelector((s) => ({ id: s.user.id, name: s.user.name }));

// Correct: pass equality or select primitives separately

hummbit/react.configureGlobalStore

Same function exported from React entrypoint for convenience.

Example 1: Disable devtools

import { configureGlobalStore } from "hummbit/react";

configureGlobalStore({ devtools: false });

Example 2: Name devtools instance

configureGlobalStore({ devtools: { enabled: true, name: "react-global" } });

Example 3: Add middleware

import { configureGlobalStore, type Middleware } from "hummbit/react";

const mw: Middleware<any> = () => ({
  afterUpdate: () => console.log("updated"),
});
configureGlobalStore({ middleware: [mw] });

Example 4: Boot file setup

export const setupStore = () => {
  configureGlobalStore({ devtools: true });
};

Example 5: Anti-pattern vs correct

// Anti-pattern: reconfigure repeatedly in components
// Correct: configure once in app bootstrap

hummbit/react.initStore

Creates store instance and adds store.useSelector.

Example 1: Basic React store

import { initStore } from "hummbit/react";

export const store = initStore({
  initialState: { count: 0 },
  actions: ({ actionCreator, setState }) => ({
    inc: actionCreator("inc", () =>
      setState((s) => ({ ...s, count: s.count + 1 })),
    ),
  }),
  selectors: { count: (s) => s.count },
});

Example 2: Consume with store.useSelector

function Counter() {
  const count = store.useSelector(store.selectors.count);
  return <button onClick={() => store.actions.inc()}>{count}</button>;
}

Example 3: Custom equality in store selector

const profile = store.useSelector(
  (s) => s.profile,
  (a, b) => a.id === b.id && a.version === b.version,
);

Example 4: Async action pattern

const asyncStore = initStore({
  initialState: { loading: false, data: null as null | string },
  actions: ({ actionCreator, setState }) => ({
    load: actionCreator("load", async () => {
      await setState((s) => ({ ...s, loading: true }));
      const data = await Promise.resolve("ok");
      await setState((s) => ({ ...s, loading: false, data }));
    }),
  }),
  selectors: { data: (s) => s.data, loading: (s) => s.loading },
});

Example 5: Anti-pattern vs correct

// Anti-pattern: store business logic in component local state only
// Correct: centralize shared domain updates in initStore actions
Author: Alexey TolkachevLinkedIn