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. 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 or @mirafive/sdk-tanstack instead: they create the client for you and add the server side.
Install
npm install @mirafive/sdk-react @mirafive/sdk-browserRequires 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 for where to find it. Put it in your bundler's public env var:
VITE_MIRAFIVE_KEY=mf_…Create the client once, in the browser entry, and wrap the app in MiraProvider:
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: it sets no cookies, stores nothing on the device and needs no consent banner.
Verify
- Open the app on its real domain. Events from
localhostare dropped unless you passtrackLocalhost: truetocreateMira. - In the browser's network tab, look for
POST https://events.mirafive.io/v1/batch/mf_…answering202with"accepted": 1. Batches leave 5 seconds after the first event, or when the tab is hidden. - Open the source's live view in MIRA FIVE and find the pageview.
Nothing arriving? See Troubleshooting.
Track events
Read the client with useMira() and call track(name, properties):
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:
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.
Identify users
Identifying users needs full mode and the identity() plugin. Add both to createMira, then pass your consent manager's answer and the user id:
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()],
})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 and Consent.
Feature flags
With the flags() plugin on the client, read a flag's variant with useFlag and its remote config with useFlagConfig:
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 where the browser SDK would. Hooks re-render only when an answer changes. See 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:
// 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 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
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. Typed events work through useMira<Events>():
type Events = { signup: { plan: string }; logout: undefined }
const mira = useMira<Events>()
mira.track('signup', { plan: 'pro' }) // checked by TypeScriptThe 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:
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.