> [Agent navigation index](/llms.txt)

# Stores

`createStore` provides request-safe SSR state and in-memory browser state shared across independently
hydrated Preact islands. Use it when multiple islands on one page need the same reactive model. Keep
state local with `useSignal` when only one component owns it; use [storage](/docs/storage) when values
must survive navigation, reloads, or browser tabs.

## Generate an application store

```sh
brix add store booking --feature booking

```

The declaration belongs to the application that hydrates it:

```text
src/features/booking/
  booking.store.ts
  BookingDetails.tsx
  BookingSummary.tsx

```

A store module is `*.store.ts`, not `.tsx`, and has one runtime export: the destructured named tuple
returned by `createStore`. The provider and hook must share a stem:

```ts
import { useSignal } from "brix/hooks";
import { createStore } from "brix/islands";

type Booking = { guestName: string; status: string };

export const [BookingStoreProvider, useBookingStore] = createStore(
  ({ initialBooking }: { initialBooking: Booking }) => {
    const booking = useSignal(initialBooking);
    return {
      booking,
      confirm: () => {
        booking.value = { ...booking.value, status: "confirmed" };
      },
    };
  },
);

```

The provider name ends in `StoreProvider`; the hook starts with `use` and ends in `Store`. Type-only
exports are allowed. Brix's application build transform injects a stable identity into conventional
store modules. Do not provide a manual key in application code.

A library can share setup functions, domain operations, and types, but should not export the
`createStore` tuple intended to span another application's islands. Generate the thin tuple in the
owning application so its build assigns the identity.

## Render one provider

Place one provider for the store above all consumers:

```tsx
<BookingStoreProvider initialBooking={booking}>
  <BookingDetails />
  <BookingSummary />
  <BookingBadge />
</BookingStoreProvider>

```

The provider itself hydrates as an island. Children cross its server-rendered slot and do not all
become part of the provider's browser bundle. See [islands](/docs/islands) for slot and hydration
boundaries.

Provider props use island serialization. Pass supported serializable data, including primitives,
plain objects, arrays, and supported rich values; do not pass functions, symbols, arbitrary class instances, request contexts, or services. The setup
return value stays in memory and is not serialized, so it may contain signals, functions, and class
instances.

Only one provider for a store should be mounted on a page. A second active provider with the same
identity logs an error because ownership and initialization would be ambiguous.

## Consume state safely

Store setup runs in a Preact render context, so supported hooks can initialize the model. Import
application component hooks from `brix/hooks`.

```tsx
"use island";

import { useBookingStore } from "./booking.store";

export function BookingDetails() {
  const store = useBookingStore();
  if (!store) return null;

  return (
    <button type="button" onClick={store.confirm}>
      {store.booking.value.guestName}: {store.booking.value.status}
    </button>
  );
}

```

`useBookingStore()` returns the setup state or `null`. It is `null` before the provider has rendered,
so every consumer must handle that state. A non-island server component may call the same hook when a
provider is above it during SSR.

## Cross-island state and request isolation

Each island hydrates into a separate Preact tree, so ordinary context does not cross island roots.
Stores use a shared signal registry in the browser, persisted by stable store identity, allowing an
update in `BookingDetails` to render `BookingSummary` even though they hydrate separately.

SSR uses a renderer-local registry instead of a process-global value. Concurrent requests and later
requests cannot observe one another's provider state. The server state initializes browser state
through the provider's serialized props; mutable setup return values themselves do not cross that
boundary.

This makes stores suitable for page-scoped UI coordination, not server persistence, global caches,
or user sessions. For server data freshness and cache ownership, see [caching](/docs/caching).

## Test a store

Use a browser component test to exercise actual reactive sharing. A manual second argument to
`createStore` is reserved for direct tests and should be unique per test:

```ts
import { expect, it } from "brix/test";
import { page, render } from "brix/test/browser";
import { h } from "brix/preact";
import { createStore } from "brix/islands";

it("shares state between roots", async () => {
  const setup = ({ value }: { value: string }) => ({ value });
  const [Provider] = createStore(setup, "booking-test");
  const [, useStore] = createStore(setup, "booking-test");

  function Consumer() {
    return h("span", { "data-testid": "value" }, useStore()?.value ?? "missing");
  }

  render(h(Provider, { value: "shared" }));
  render(h(Consumer, null));

  await expect.element(page.getByTestId("value")).toHaveTextContent("shared");
});

```

Also test the `null` fallback and provider initialization props. Route tests can verify SSR markup;
browser tests prove sharing after hydration.

```sh
brix check
brix test --project browser
brix build

```

See [testing](/docs/testing) for component and route harness choices.
