Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/iDevRanjan/nexus-store/llms.txt

Use this file to discover all available pages before exploring further.

useStore is the bridge between your NexusStore stores and your React components. Call it inside any component to subscribe to state — the component will automatically re-render whenever the data it cares about changes, and stay silent when unrelated parts of the store update.

How useStore works

Under the hood, useStore delegates to React’s built-in useSyncExternalStore hook, which is specifically designed for integrating external (non-React) state containers with React’s concurrent rendering model:
src/NexusStore/useStore.js
import { useCallback, useSyncExternalStore } from "react";

export function useStore(storeRef, selector) {
    if (selector !== undefined && typeof selector !== "function") {
        throw new Error(
            `[Store Error]: Invalid selector type.\n\n` +
                `Expected: "function"\n` +
                `Received: "${typeof selector}"\n\n` +
                `Usage Examples:\n` +
                `- Specific state: useStore(store, (state) => state.value)\n` +
                `- Complete store: useStore(store)`,
        );
    }

    const getSnapshot = useCallback(() => {
        const fullState = storeRef.getState();
        return selector ? selector(fullState) : fullState;
    }, [storeRef, selector]);

    return useSyncExternalStore(storeRef.subscribe, getSnapshot);
}
On each render, useSyncExternalStore calls getSnapshot to read the current value and compares the result with the previous snapshot using Object.is. If the value has not changed, the component skips re-rendering entirely.

Two usage patterns

You can call useStore in two ways depending on how much of the store your component needs.
Omit the selector to subscribe to the entire state object. The component will re-render whenever any key in the store changes — even keys it doesn’t read.
Subscribe to the full store
import { useStore } from "nexus-store";
import { countStore } from "./countStore";

function CountDisplay() {
  // Re-renders on ANY change to countStore
  const state = useStore(countStore);

  return <p>Count: {state.count}</p>;
}
This is convenient for small stores with only one or two keys, but can cause excess re-renders in larger stores.

Real-world examples

The source components demonstrate both approaches in practice. CountComponent subscribes to the count key in isolation. Even if other keys were added to countStore, this component would only re-render when count itself changes:
src/components/CountComponent.jsx
import { useStore } from "../NexusStore/useStore";
import { createStore } from "../NexusStore/core";

const countStore = createStore({
    count: 0,
});

export default function CountComponent() {
    const countValue = useStore(countStore, (state) => state.count);

    return (
        <fieldset>
            <legend>Count</legend>
            <p>{countValue}</p>
            <button
                onClick={() =>
                    countStore.setState(
                        (keys) => keys.count,
                        (curr) => curr + 1,
                    )
                }
            >
                Increment
            </button>
            <button
                onClick={() =>
                    countStore.setState(
                        (keys) => keys.count,
                        (curr) => Math.max(0, curr - 1),
                    )
                }
            >
                Decrement
            </button>
        </fieldset>
    );
}
ThemeComponent uses the useThemeStore custom hook from hooks.js, which itself wraps useStore. The selector drills straight to the theme string:
src/components/ThemeComponent.jsx
import { useThemeStore } from "../store/hooks";
import { themeStore } from "../store/store";

export default function ThemeComponent() {
    const theme = useThemeStore((state) => state.theme);

    return (
        <fieldset>
            <legend>Theme</legend>
            <p>Current Theme: {theme}</p>
            <button
                onClick={() =>
                    themeStore.setState(
                        (keys) => keys.theme,
                        theme === "light" ? "dark" : "light",
                    )
                }
            >
                Toggle Theme
            </button>
        </fieldset>
    );
}

The selector function

The selector is an ordinary function with the signature (state) => value. It can return anything:
Selector examples
// Return a primitive
useStore(countStore, (state) => state.count);

// Return a nested value
useStore(store, (state) => state.auth.isLoggedin);

// Return a derived value
useStore(store, (state) => state.cart.items.length);

// Return a sub-object (see selector stability note below)
useStore(store, (state) => state.cart);
When a selector returns an object or array, useSyncExternalStore compares it by reference using Object.is. If your selector creates a new object on every call (e.g. (state) => ({ ...state.cart })), it will trigger a re-render every time — even if the data is identical. Return the existing reference when possible.

Error behavior

useStore validates the selector before use. Passing a non-function value as the second argument throws an error immediately with a descriptive message:
Error thrown for invalid selector type
// This throws:
useStore(countStore, "count");

// Error:
// [Store Error]: Invalid selector type.
//
// Expected: "function"
// Received: "string"
//
// Usage Examples:
// - Specific state: useStore(store, (state) => state.value)
// - Complete store: useStore(store)
Passing undefined as the selector is always safe — that is how the full-store pattern works.

Best practices

1

Always use a selector

Even for small stores, a selector makes your component’s data dependency explicit and prevents re-renders from unrelated state changes.
// ✅ Preferred — component is insulated from other keys
const count = useStore(countStore, (state) => state.count);

// ⚠️  Acceptable only for single-key stores
const state = useStore(countStore);
2

Keep selectors stable

Inline arrow functions create a new function reference on every render. While useStore wraps getSnapshot in useCallback, an unstable selector will still defeat memoisation. For selectors that return objects or arrays, define them outside the component or wrap them with useCallback.
// ✅ Stable — defined outside the component
const cartItemsSelector = (state) => state.cart.items;

function CartComponent() {
  const items = useStore(store, cartItemsSelector);
  // ...
}
// ✅ Also stable — memoised with useCallback
const items = useStore(
  store,
  useCallback((state) => state.cart.items, [])
);
See Selector Optimisation for a deeper look at referential stability.
3

Use custom hooks per store

Wrapping useStore in a custom hook (as hooks.js does with useThemeStore and useAuthStore) hides the store reference from components and gives you a single place to update the selector signature if your state shape changes.
src/store/hooks.js
export function useThemeStore(selector) {
    return useStore(themeStore, selector);
}

Build docs developers (and LLMs) love