# react/ — @cloudflare/realtimekit-react

Thin React wrapper (~2 source files) around `@cloudflare/realtimekit` providing a context provider, lifecycle hook, selector hook, and media pre-init utility.

## PUBLIC API

| Export | File | Purpose |
|--------|------|---------|
| `RealtimeKitProvider` | `src/context.tsx` | Context provider; renders `fallback` when `value` is falsy |
| `useRealtimeKitMeeting()` | `src/context.tsx` | Returns `{ meeting: RTKClient }`; throws outside provider |
| `useRealtimeKitClient(params?)` | `src/index.ts` | Manages client lifecycle; returns `[client, initClient]` |
| `useRealtimeKitSelector(selector)` | `src/index.ts` | Extracts + subscribes to a slice of `RTKClient` state |
| `initRTKMedia(options?, skipAwaits?, cachedUserDetails?)` | `src/index.ts` | Pre-inits media before `RTKClient.init`; wraps `RTKClient.initMedia` |

## REACTIVITY MODEL

`RealtimeKitProvider` creates an `RTKUpdates` instance (internal class, `src/context.tsx:12`) that attaches wildcard `'*'` listeners to every major SDK sub-module (`self`, `meta`, `participants`, `participants.joined/active/waitlisted`, `stage`, `livestream`, `chat`, `plugins.all`, `recording`, `connectedMeetings`, `polls`). Any event on any sub-module fans out to all registered `RTKUpdates` listeners.

`useRealtimeKitSelector` has two subscription modes:
1. **Direct** — if `selector(meeting)` returns an object with `addListener`, subscribes `'*'` on that object directly (e.g. `meeting.participants.joined`).
2. **Global fallback** — otherwise subscribes to the `RTKUpdates` bus.

Re-renders use a `useReducer` counter (`forceUpdate`) to bypass React's shallow-equality bailout when the same object reference is mutated.

`shouldUpdate` (`src/index.ts:125`) gates re-renders: fires if reference differs, or if both sides are arrays with different lengths.

## WHERE TO LOOK

| Task | Location |
|------|---------|
| Provider + `useRealtimeKitMeeting` | `src/context.tsx` |
| `useRealtimeKitClient`, `useRealtimeKitSelector`, `initRTKMedia` | `src/index.ts` |
| All re-exported core types | `src/index.ts:177–238` |
| Demo app (Chat, Meeting, Participants) | `example/` |
| Publish-time `package.json` rewrite | `prepublish.js` |

## GOTCHAS

- **100ms null-flash on `meetingChanged`** (`src/index.ts:108`): `useRealtimeKitClient` sets `client` to `undefined` then restores the new client after 100ms via `setTimeout`. This is intentional — React won't re-render when an object reference changes to a same-shape new object. Any UI reading `client` will briefly see `undefined`.

- **`cachedUserDetails` is experimental** (`src/index.ts:61`): The third param to `initRTKMedia` skips blocking network calls (chat, polls, etc.) using cached data. Not production-hardened; may silently miss fresh data.

- **`prepublish.js` rewrites `package.json`** before npm publish: pins `@cloudflare/realtimekit` dependency to the exact build version, strips dev scripts, sets `private: false`, and rewrites `publishConfig.tag` based on the `ENVIRONMENT` env var (`staging` → `tag: "staging"`, else `latest`). Never manually edit the published `package.json`.

- **`RTKUpdates.clean()` is called on provider unmount** but only via `updatesRef.current` — note `updatesRef` is never assigned in `context.tsx` (the `useRef` is unused). `setUpdates` state is what propagates `updates` to context; the `clean()` call on unmount uses the stale ref and is a no-op. Wildcard listeners are not removed on `value` change.
