> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sublay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Redux Integration

> Merge Sublay's Redux state into your existing store using SublayIntegrationProvider

By default, `SublayProvider` creates and manages its own isolated Redux store. This is the recommended approach for most projects — no Redux configuration required.

If your application already has its own Redux store, we strongly recommend merging Sublay's state into it using `SublayIntegrationProvider`. Running two separate Redux stores in the same React app is a common source of subtle bugs: React-Redux binds components to the nearest `<Provider>` in the tree, so hooks can silently read from the wrong store depending on where components are rendered.

## When to Use This

**If your app already has a Redux store, use `SublayIntegrationProvider`.** This is the right path for virtually every app in that situation. The standalone `SublayProvider` is only appropriate for apps that don't use Redux at all.

The standalone provider creates its own internal store. When that coexists with your app's store, React-Redux binds components to whichever `<Provider>` is closest in the tree — which means hooks silently read from the wrong store depending on where a component is rendered. These bugs are hard to spot and hard to trace.

The only reason to use `SublayProvider` alongside an existing Redux store is if you have a specific, deliberate reason to keep the two stores completely separate and you fully understand the implications. For nearly everyone, that's not the case.

## Setup

Integration mode requires three changes to your existing store and one change to your provider setup.

<Steps>
  <Step title="Add Sublay reducers to your store">
    Import `sublayReducers` and `sublayApiReducer` from `@sublay/react-js` and add them to your store's `reducer` map. The keys must be exactly `sublay` and `sublayApi` — the SDK's internal selectors depend on these names.

    ```ts theme={null}
    import { configureStore } from "@reduxjs/toolkit";
    import {
      sublayReducers,
      sublayApiReducer,
      sublayMiddleware,
    } from "@sublay/react-js";
    import yourReducer from "./yourSlice";

    const store = configureStore({
      reducer: {
        // Your own reducers
        yourFeature: yourReducer,

        // Sublay — keys must be exactly these
        sublay: sublayReducers,
        sublayApi: sublayApiReducer,
      },
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(...sublayMiddleware),
    });

    export default store;
    export type RootState = ReturnType<typeof store.getState>;
    export type AppDispatch = typeof store.dispatch;
    ```
  </Step>

  <Step title="Replace SublayProvider with SublayIntegrationProvider">
    Use `SublayIntegrationProvider` instead of `SublayProvider`. This provider does not create a Redux store — it expects your own `<Provider>` to already be in the tree above it.

    <Warning>
      **Import it from your platform package** — `@sublay/react-js`,
      `@sublay/react-native` or `@sublay/expo` — **not from `@sublay/core`.**

      Account persistence is carried by an `AccountManager` component that only
      the platform packages can supply, because the storage adapter is
      platform-specific. Core's own `SublayIntegrationProvider` has no
      `AccountManager`: it works, but the account map lives only in memory, so
      stored accounts (and the per-device push state that rides on the same
      map) do not survive a relaunch. The platform packages' version wraps
      core's and mounts the right adapter for you.
    </Warning>

    ```tsx theme={null}
    import { Provider } from "react-redux";
    import { SublayIntegrationProvider } from "@sublay/react-js";
    import store from "./store";

    function App() {
      return (
        <Provider store={store}>
          <SublayIntegrationProvider projectId="your-project-id">
            {/* your app */}
          </SublayIntegrationProvider>
        </Provider>
      );
    }
    ```
  </Step>
</Steps>

## Props

`SublayIntegrationProvider` accepts the same props as `SublayProvider`:

<ParamField body="projectId" type="string" required>
  Your Sublay project ID.
</ParamField>

<ParamField body="signedToken" type="string | null">
  A signed JWT for external authentication mode. See [External Auth](/v8/sdk/authentication/external).
</ParamField>

## Exports Reference

All integration exports are available from `@sublay/react-js`:

| Export | Type | Purpose |
| - | - | - |
| `sublayReducers` | `Reducer` | Combined reducer for all Sublay feature slices. Mount under the `sublay` key. |
| `sublayApiReducer` | `Reducer` | RTK Query API reducer. Mount under the `sublayApi` key. |
| `sublayMiddleware` | `Middleware[]` | Every middleware Sublay requires — the RTK Query middleware that drives cache invalidation and subscriptions, plus the SDK's own. Spread it into your middleware chain; the pieces are not published individually. |
| `sublayApi` | `Api` | The RTK Query API object itself — distinct from the `sublayApi` *reducer key* it is mounted under. Use `sublayApi.util` for cache operations such as `resetApiState()`. |
| `SublayIntegrationProvider` | `React.FC` | Context provider for integration mode. |
| `resetAccountScopedState` | `ActionCreator` | Dispatched by the SDK on every account change. Subscribe your own slices to it — see below. |
| `resetAuth` / `clearUser` | `ActionCreator` | The auth and current-user teardown actions. |
| `activateStoredAccount` | `function` | Validates a stored account's credential, then tears the current session down and makes that account active. Takes `dispatch` **and `getState`** — both are required. Rejects with an `AccountTransitionError` when the credential cannot be exchanged, leaving the live session, the selection and both entries untouched; the only writes a failure makes are a `needsReauth` mark on the target when the credential was refused, and `signedOut` when no account was active. A plain function, callable from a thunk or effect body. |
| `AccountTransitionError` | `class` | What `activateStoredAccount` rejects with, and what the `switchAccount` / `removeAccount` hooks reject with too. Two independent discriminants: `credentialRejected` separates a dead account from a failed request, and `accountNotFound` marks an id that is not in the stored map at all — a stale id, where nothing was attempted and a re-auth would not help. See [Telling a dead account from a dead network](/v8/sdk/authentication/multi-account#telling-a-dead-account-from-a-dead-network). |
| `ACCOUNT_TRANSITION_FAILED_MESSAGE` | `string` | The default message on that error, for matching or re-use in your own copy. |
| `ACCOUNT_LIMIT_MESSAGE` | `string` | The message the sign-in paths reject with when the device already remembers `MAX_ACCOUNTS` accounts. |
| `setSignedOut` | `ActionCreator` | Records that ending the session was deliberate. Dispatch `setSignedOut(true)` in a hand-rolled teardown — the three teardown actions above only clear the session, so without it the next launch reads "nothing was ever selected", re-activates the account and signs the app back in. `setActiveAccount` clears it again on the next activation. |
| `setSublayLogLevel` | `function` | Sets SDK console logging to `"error"` (default), `"warn"` or `"silent"`. Process-global; not a provider prop. |
| `selectAccounts`, `selectActiveAccountId`, `selectSignedOut`, `selectAccountLimitReached`, `selectDeviceIdentifier` | `Selector` | Selectors over the accounts slice. |
| `MAX_ACCOUNTS` | `number` | The stored-account limit (5). |
| `wouldExceedAccountLimit` | `function` | `(accounts, userId?) => boolean` — whether admitting `userId` into a raw accounts map would exceed `MAX_ACCOUNTS`. The exact rule the SDK's own refusal gates apply, including its one exception: an id already in the map is **not** an admission, so it answers `false` even at the cap. Pure and read-only. See [Three signals for the account cap](/v8/sdk/authentication/multi-account#three-signals-for-the-account-cap). |
| `isAccountPushEnabled` | `function` | Reads an account entry's per-device push preference **for display**. Use it rather than testing `entry.pushEnabled`: an absent flag means "never expressed a preference", which reads as enabled. It is what a per-account switch renders as `checked`, and is not the rule the SDK uses to decide whether to create a binding — that one requires an explicit opt-in and is internal, so no binding is ever created for an account that has not asked for one. |
| `accountNeedsReauth` | `function` | Reads an account entry's "this credential is dead" marker — set when a transition into that account was refused, and set without a network call when the entry carries no usable credential at all. |
| `accountNeedsPushRebind` | `function` | Reads an account entry's "notifications are paused until this account is next opened" marker. Distinct from `accountNeedsReauth` — see [`useAccounts`](/v8/hooks/auth/use-accounts). |
| `AccountStorage` | `type` | The storage contract an `AccountManager` implements. See [The storage contract](#the-storage-contract) for what an implementation must guarantee. |

<Note>
  The raw `setActiveAccount`, `upsertAccount` and `removeAccount` reducers are
  deliberately **not** exported. They only mutate local state: `setActiveAccount`
  can point the active id at an account that is not in the map, and
  `removeAccount` drops an account without signing it out server-side. Use
  `activateStoredAccount` and the `useRemoveAccount` hook, which perform the
  whole flow.
</Note>

## The Storage Contract

`AccountStorage` is the interface an `AccountManager` implements, and the platform packages
supply one each. If you write your own, three rules govern it:

```ts theme={null}
interface AccountStorage {
  getAccountMap(projectId: string): Promise<AccountMap | null>;
  setAccountMap(projectId: string, map: AccountMap): Promise<void>;
  deleteAccountMap(projectId: string): Promise<void>;
}
```

* **`getAccountMap` is tolerant.** An unreadable, corrupt or unrecognized store resolves `null`
  rather than throwing. Nothing useful can be done with a read failure at the call sites, and a
  throw takes the whole bootstrap down.
* **`setAccountMap` and `deleteAccountMap` must reject when the write does not land.** Logging the
  failure on the way out is fine; resolving anyway is not. The SDK awaits these writes and treats a
  resolved promise as "this is durably stored" — most consequentially for a rotated refresh token,
  where believing a failed write means re-presenting a revoked token and destroying that account's
  whole token family.
* **No locking of your own is needed.** Core serializes every call per `projectId`.

One project per app is the supported shape, and account storage enforces it: mounting a second
provider for a *different* project id throws at mount, with a message naming both project ids. See
[One project per app](/v8/sdk/authentication/multi-account#one-project-per-app).

## Clearing Your Own State on an Account Change

Sublay dispatches `resetAccountScopedState` on **every** path that changes the active account — switching, removing, signing out, deleting an account, adding one, a direct sign-in while another account is active, and cross-tab sync. Its own feature slices return to their initial state on it.

If you keep account-scoped data in your own slices, subscribe to the same signal instead of re-deriving when an account changed:

```ts theme={null}
import { resetAccountScopedState } from "@sublay/react-js";

const yourFeatureSlice = createSlice({
  name: "yourFeature",
  initialState,
  reducers: { /* ... */ },
  extraReducers: (builder) => {
    builder.addCase(resetAccountScopedState, () => initialState);
  },
});
```

RTK Query caches are handled separately by `sublayApi.util.resetApiState()`, which the SDK dispatches alongside.

## State Shape

When integrated, Sublay occupies two top-level keys in your store:

```ts theme={null}
{
  sublay: {
    auth: { ... },          // Auth tokens, current user, initialization state
    accounts: { ... },      // Multi-account list, active account, signedOut,
                            // accountLimitReached
    user: { ... },          // Current user profile
    appNotifications: { ... },
    collections: { ... },
    entityLists: { ... },
    spaceLists: { ... },
    chat: { ... },
    tables: { ... },
  },
  sublayApi: { ... },      // RTK Query cache (collections, entity lists, etc.)
  
  // Your own slices
  yourFeature: { ... },
}
```

<Note>
  The `sublay` and `sublayApi` keys are required by Sublay's internal selectors. Mounting the reducers under different keys will cause the SDK to malfunction.
</Note>

## TypeScript

Sublay exports `SublayState` if you need to type a state slice that references Sublay's shape:

```ts theme={null}
import type { SublayState } from "@sublay/react-js";

interface RootState {
  sublay: SublayState;
  sublayApi: unknown; // RTK Query manages this type internally
  yourFeature: YourFeatureState;
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.