> For the complete documentation index, see [llms.txt](https://codemash.gitbook.io/codemash/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://codemash.gitbook.io/codemash/sdks/react-redux.md).

# React Redux

Read and write Norbix records from a React app with hooks, caching and refetch (React + Redux Toolkit).

[`@norbix.ai/react-redux`](https://www.npmjs.com/package/@norbix.ai/react-redux) (source: [github.com/norbix-code/react-redux](https://github.com/norbix-code/react-redux)) gives a React app hooks for Norbix, built with Redux Toolkit's RTK Query on top of the [JavaScript / TypeScript SDK](/codemash/sdks/javascript-typescript.md). You get caching, one request for the same data, and automatic refetch: after an insert into `books`, every list of `books` on the screen loads again. The [Admin portal](/codemash/admin/admin.md) is built on it.

This page was run on 2026-10-06 with `@norbix.ai/react-redux` **1.8.2**, `@norbix.ai/ts` 4.13.0, React 19 and Redux Toolkit 2: the four files below were type-checked (`tsc --strict`), built the way Vite builds them, opened in a test page (jsdom), and both buttons were clicked.

## Before you start

* A React 18 (or newer) app, built with Vite in the examples below.
* A project with a `books` collection — `title` (text, required), `pages` (number, required), `in_stock` (yes / no), `genre`. Create it in Cloud as in step 3 of the [Quickstart](/codemash/get-started/managed-service/quickstart.md).
* Your project id, and for an internal tool a key — see [Install a Norbix SDK](/codemash/sdks/install.md).

## Install

```sh
npm install @norbix.ai/react-redux @norbix.ai/ts @reduxjs/toolkit react-redux react
```

## Create the client

A web page has no environment variables, so the values come from your build. With Vite, put them in `.env.local` as `VITE_NORBIX_PROJECT_ID` and `VITE_NORBIX_API_KEY`.

`src/norbix.ts`:

```ts
import { Norbix } from '@norbix.ai/ts';
import { createNorbixApi } from '@norbix.ai/react-redux';

export const norbix = new Norbix({
  projectId: import.meta.env.VITE_NORBIX_PROJECT_ID, // pr_…
  // A key in the browser is for internal tools only. In a public app, sign the
  // user in instead (useLoginMutation) and leave apiKey out.
  apiKey: import.meta.env.VITE_NORBIX_API_KEY,
});

export const norbixApi = createNorbixApi(() => norbix);
export const { useFindCollectionQuery, useInsertOneMutation } = norbixApi;
```

{% hint style="danger" %}
Everything in a web page can be read by its visitors — a key there is public. Use a key only in a tool that runs for your own team. In an app for your users, sign them in with `useLoginMutation` (the [Admin portal](/codemash/admin/admin/sign-in.md) shows the full flow) and leave `apiKey` out.
{% endhint %}

`src/store.ts`:

```ts
import { configureStore } from '@reduxjs/toolkit';
import { norbixApi } from './norbix';

export const store = configureStore({
  reducer: { [norbixApi.reducerPath]: norbixApi.reducer },
  middleware: (getDefault) => getDefault().concat(norbixApi.middleware),
});
```

`src/main.tsx` — wrap the app in both providers:

```tsx
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { NorbixProvider } from '@norbix.ai/react-redux';
import { store } from './store';
import { norbix } from './norbix';
import { Books } from './Books';

createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <NorbixProvider client={norbix}>
      <Books />
    </NorbixProvider>
  </Provider>,
);
```

## Read, write and handle errors

`src/Books.tsx` reads the books with `useFindCollectionQuery`, writes one with `useInsertOneMutation`, and shows the error of a failed write. A failed call gives `{ code, message, status, errors }`.

```tsx
import { useState } from 'react';
import { useFindCollectionQuery, useInsertOneMutation } from './norbix';

export function Books() {
  // Read: the list refetches by itself after every insert into `books`.
  const { data, isLoading, error } = useFindCollectionQuery({ collectionName: 'books', pageSize: 10 });
  // Write
  const [insertOne, { isLoading: saving }] = useInsertOneMutation();
  const [failure, setFailure] = useState<string>();

  const add = async (book: object) => {
    try {
      await insertOne({ collectionName: 'books', document: JSON.stringify(book) }).unwrap();
      setFailure(undefined);
    } catch (err) {
      // Error handling: err is { code, message, status, errors }
      const e = err as { code?: string; message: string };
      setFailure(`${e.code} — ${e.message}`);
    }
  };

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <ul>
        {data?.list?.items.map((book: any) => (
          <li key={book._id}>{book.title} — {book.pages} pages</li>
        ))}
      </ul>
      <button disabled={saving} onClick={() => add({ title: 'Dune', pages: 412, genre: 'fiction' })}>
        Add Dune
      </button>
      <button disabled={saving} onClick={() => add({ title: 'Untitled draft' })}>
        Add a book without pages
      </button>
      {failure && <p role="alert">Failed: {failure}</p>}
    </div>
  );
}
```

## Result

What the page shows (the collection already had *The Hobbit*):

| Step                         | The page shows                                                                                      |
| ---------------------------- | --------------------------------------------------------------------------------------------------- |
| first load                   | The Hobbit — 310 pages                                                                              |
| **Add Dune**                 | Dune — 412 pages · The Hobbit — 310 pages (the list loaded again by itself)                         |
| **Add a book without pages** | the same list, and *Failed: CM-ERRORS-DATABASE-030 — Field 'pages' is invalid: a value is required* |

## Other hooks

The package has a hook for every Api database method and every Hub database method, plus hooks for users, sign-in, account, projects, email templates and files — the full list is in the package README. For a call without a hook, `useNorbix()` gives you the SDK client inside a component.

## Next steps

* The SDK underneath: [JavaScript / TypeScript](/codemash/sdks/javascript-typescript.md).
* A complete app built on this package: [Admin](/codemash/admin/admin.md).
