# React

Source: https://docs.mirafive.io/sdks/react

> Add MIRA FIVE analytics and feature flags to a React app with a provider, flag hooks and a mount tracker.

`@mirafive/sdk-react` adds a provider, feature-flag hooks and a mount tracker on top of the [browser SDK](https://docs.mirafive.io/sdks/browser). Use it for React apps built with Vite, Create React App, React Router or any other client setup. Using Next.js or TanStack Start? Use [`@mirafive/sdk-next`](https://docs.mirafive.io/sdks/nextjs) or [`@mirafive/sdk-tanstack`](https://docs.mirafive.io/sdks/tanstack-start) instead: they create the client for you and add the server side.

## Install

```bash
npm install @mirafive/sdk-react @mirafive/sdk-browser
```

Requires React 18.3 or 19. The package is ESM only and adds 0.84 kB (min + gzip) on top of the browser SDK.

## Set up

You need the **website key** of a website source (`mf_…`). See [Keys](https://docs.mirafive.io/keys) for where to find it. Put it in your bundler's public env var:

```sh title=".env"
VITE_MIRAFIVE_KEY=mf_…
```

Create the client once, in the browser entry, and wrap the app in `MiraProvider`:

```tsx title="src/main.tsx"
import { createMira } from '@mirafive/sdk-browser'
import { flags } from '@mirafive/sdk-browser/flags'
import { pageviews } from '@mirafive/sdk-browser/pageviews'
import { MiraProvider } from '@mirafive/sdk-react'
import { createRoot } from 'react-dom/client'

import { App } from './App'

const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  plugins: [pageviews(), flags()],
})

createRoot(document.getElementById('root')!).render(
  <MiraProvider client={mira}>
    <App />
  </MiraProvider>,
)
```

That is the whole install. `pageviews()` records the first page and every client-side navigation for any router, through the Navigation API or the History API. You do not add a router hook.

The client starts in [consentless mode](https://docs.mirafive.io/guides/consent): it sets no cookies, stores nothing on the device and needs no consent banner.

## Verify

1. Open the app on its real domain. Events from `localhost` are dropped unless you pass `trackLocalhost: true` to `createMira`.
2. In the browser's network tab, look for `POST https://events.mirafive.io/v1/batch/mf_…` answering `202` with `"accepted": 1`. Batches leave 5 seconds after the first event, or when the tab is hidden.
3. Open the source's live view in MIRA FIVE and find the pageview.

Nothing arriving? See [Troubleshooting](#troubleshooting).

## Track events

Read the client with `useMira()` and call `track(name, properties)`:

```tsx title="src/Checkout.tsx"
import { useMira } from '@mirafive/sdk-react'

export function Checkout() {
  const mira = useMira()

  return <button onClick={() => mira.track('checkout_started', { plan: 'pro' })}>Checkout</button>
}
```

To record one event when a component appears, use `useTrackOnMount`. It sends once, also under StrictMode:

```tsx
import { useTrackOnMount } from '@mirafive/sdk-react'

export function PricingPage() {
  useTrackOnMount('pricing_viewed', { source: 'nav' })
  return <h1>Pricing</h1>
}
```

Event names, property limits and revenue are covered in [Track events](https://docs.mirafive.io/guides/track-events).

## Identify users

Identifying users needs [full mode](https://docs.mirafive.io/guides/consent#full-mode) and the `identity()` plugin. Add both to `createMira`, then pass your consent manager's answer and the user id:

```tsx
import { createMira } from '@mirafive/sdk-browser'
import { flags } from '@mirafive/sdk-browser/flags'
import { identity } from '@mirafive/sdk-browser/identity'
import { pageviews } from '@mirafive/sdk-browser/pageviews'

const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  mode: 'full',
  plugins: [pageviews(), identity(), flags()],
})
```

```tsx
const mira = useMira()

// From your consent manager's callback:
mira.consent({ statistics: true, experiments: true, targeting: false })

// After login:
mira.identify(user.id, { plan: user.plan })

// After logout:
mira.reset()
```

Before a consent answer, full mode stores and sends nothing. See [Identify users](https://docs.mirafive.io/guides/identify-users) and [Consent](https://docs.mirafive.io/guides/consent).

## Feature flags

With the `flags()` plugin on the client, read a flag's variant with `useFlag` and its remote config with `useFlagConfig`:

```tsx
import { useFlag, useFlagConfig } from '@mirafive/sdk-react'

export function BuyButton() {
  const newCheckout = useFlag('new-checkout', false)
  const limits = useFlagConfig('checkout-limits', { maxItems: 10 })

  return <button>{newCheckout === true ? 'Buy now' : `Checkout (max ${limits.maxItems})`}</button>
}
```

`useFlag` returns the variant key, or `true`/`false` for an on/off flag. It returns the fallback until flags load, and counts an [exposure](https://docs.mirafive.io/guides/experiments) where the browser SDK would. Hooks re-render only when an answer changes. See [Feature flags](https://docs.mirafive.io/guides/feature-flags).

## Server rendering

`createMira()` needs a browser. If your app renders on a server, pass `client={undefined}` there and hand the provider the flag answers the server used. Every flag hook then returns exactly those during the server render and hydration, so both produce the same markup:

```tsx
// A module both the server and the browser load
const mira =
  typeof window === 'undefined'
    ? undefined
    : createMira({ key: import.meta.env.VITE_MIRAFIVE_KEY, plugins: [pageviews(), flags()] })

export function Root({ bootstrap }: { bootstrap: string }) {
  return (
    <MiraProvider client={mira} bootstrap={bootstrap}>
      <App />
    </MiraProvider>
  )
}
```

`bootstrap` is a `FlagBootstrap` object, or the `<script id="mirafive-flags">` block that `UserFlags.bootstrap()` from [`@mirafive/sdk-server/flags`](https://docs.mirafive.io/sdks/node#feature-flags) returns. Render that block into the page before the app's scripts too, so the browser SDK starts from the same answers. A bootstrap older than 7 days is ignored. Next.js and TanStack Start packages do all of this for you.

## API reference

```ts
import { MiraProvider, useFlag, useFlagConfig, useMira, useTrackOnMount } from '@mirafive/sdk-react'
import type { FlagBootstrap, MiraProviderProps } from '@mirafive/sdk-react'
```

| Export | Description |
| --- | --- |
| `<MiraProvider client bootstrap?>` | Makes the client available to the hooks. `client` is a `Mira` from `createMira()`, or `undefined` during a server render. `bootstrap` is a `FlagBootstrap` or the bootstrap block string; hooks read only it during a server render and hydration. |
| `useMira<Events>()` | The client. During a server render it is an inert stand-in: calls do nothing, reads return their fallback, `flush()` resolves. |
| `useFlag(key, fallback)` | `string \| boolean`. The variant, or `true`/`false` for an on/off flag. |
| `useFlagConfig<T>(key, fallback)` | `T`. The remote-config value of the flag's variant. |
| `useTrackOnMount(name, properties?)` | Tracks one event when the component mounts, with the properties of its first render. |

Everything the client can do (`track`, `identify`, `consent`, `flush`, …) is listed in the [browser SDK reference](https://docs.mirafive.io/sdks/browser#api-reference). Typed events work through `useMira<Events>()`:

```ts
type Events = { signup: { plan: string }; logout: undefined }

const mira = useMira<Events>()
mira.track('signup', { plan: 'pro' }) // checked by TypeScript
```

The package starts with `"use client"`, so it also works as a client module in React Server Components setups.

## Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| Nothing arrives | Events from `localhost` are off by default (`trackLocalhost: true`). Do Not Track or Global Privacy Control is on in the browser. The site's origin is not allowed on the source. |
| `403 origin_not_allowed` | Add the site's domain to the source in MIRA FIVE. |
| `403 secret_key_in_path` or `403 website_key_as_bearer` | The keys are swapped. Browsers use the website key, servers the secret key. |
| A flag always returns its fallback | The client has no `flags()` plugin, the flag is not enabled for this source's website, experiments consent is missing, or flags have not loaded yet. |
| Hydration mismatch on a flag | The server and the browser passed different `bootstrap` values, or only one side passed one. Pass the same one to both. |
| `render <MiraProvider> above hooks` | A hook runs outside the provider. |

## Set up with an AI agent

Paste this into your coding agent:

```text
Add MIRA FIVE analytics and feature flags to this React app with @mirafive/sdk-react.
Docs: https://docs.mirafive.io/sdks/react.md

1. Install @mirafive/sdk-react and @mirafive/sdk-browser with the project's package manager.
   If this is a Next.js app, use @mirafive/sdk-next instead; TanStack Start: @mirafive/sdk-tanstack.
2. Put the website key (mf_…) in the bundler's public env var, e.g. VITE_MIRAFIVE_KEY for Vite.
   Never put MIRAFIVE_SECRET_KEY in browser code.
3. In the browser entry, create the client once:
     createMira({ key, plugins: [pageviews(), flags()] })
   from '@mirafive/sdk-browser', '@mirafive/sdk-browser/pageviews' and '@mirafive/sdk-browser/flags',
   and wrap the app in <MiraProvider client={mira}> from '@mirafive/sdk-react'.
4. Do not add router hooks for pageviews; pageviews() covers every router.
5. Keep the default consentless mode, which needs no consent banner. Only if the app already has a
   consent manager and needs user ids: add identity() from '@mirafive/sdk-browser/identity',
   set mode: 'full', and call useMira().consent({ statistics, experiments, targeting }) from its callback.
6. Verify: load a page on a non-localhost domain (or pass trackLocalhost: true) and check the network
   tab for POST https://events.mirafive.io/v1/batch/<key> answering 202. Report what you changed.
Do not add other analytics libraries, cookies or consent banners.
```
