{"name":"@playlive/react-data","dist-tags":{"latest":"0.3.2"},"versions":{"0.3.0":{"name":"@playlive/react-data","version":"0.3.0","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.5.0","@playlive/tiltify-core":"^0.4.13"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-yXTR1HH3zKWk7hDJHxJSPffJDPQuUPwtfSgBtOi6Cdeoctt8+7MoYgkWzEP5XdqAW6Ej9vtwCh8F7oi/8ElgKw==","shasum":"d08e4669cdb6a237b9a8dd69f091f1772d9a405c","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useSchedule`         | `TiltifySchedule[]`                              | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n| `useTiltifyUserCampaigns` | `TiltifyPersonalCampaign[]`                  | `userId` is nullish / `\"null\"` |\n| `useTiltifyUserAndTeamCampaigns` | `(TiltifyPersonalCampaign \\| TiltifyTeamCampaign)[]` | `userId` is nullish / `\"null\"` |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the Twitch path rather than throwing — same lenient\nsemantics as the underlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.3.0.tgz","shasum":"d08e4669cdb6a237b9a8dd69f091f1772d9a405c","integrity":"sha512-yXTR1HH3zKWk7hDJHxJSPffJDPQuUPwtfSgBtOi6Cdeoctt8+7MoYgkWzEP5XdqAW6Ej9vtwCh8F7oi/8ElgKw=="}},"0.1.0":{"name":"@playlive/react-data","version":"0.1.0","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{"@playlive/fundraiser-data":"^0.1.0"},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.1.0","@playlive/tiltify-core":"^0.1.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-Ssw92mO+n6nMMgESJyf5iSxkagtuW/FniYSl1E0cEfoAdceVwWMAvMCrTIN5H/nLc3SA337OaeAdOqah05C2ng==","shasum":"73c69369fb4a0f7a0c00bee5e3737c702f7706cd","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioural difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useUser`, `useTeam`, `useFundraisingEvent`,\n`useCause`, `useEventCampaigns`) resolve to `[]` / `null` on the\nTwitch path rather than throwing — same lenient semantics as the\nunderlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.1.0.tgz","shasum":"73c69369fb4a0f7a0c00bee5e3737c702f7706cd","integrity":"sha512-Ssw92mO+n6nMMgESJyf5iSxkagtuW/FniYSl1E0cEfoAdceVwWMAvMCrTIN5H/nLc3SA337OaeAdOqah05C2ng=="}},"0.1.1":{"name":"@playlive/react-data","version":"0.1.1","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{"@playlive/fundraiser-data":"^0.1.0"},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.1.0","@playlive/tiltify-core":"^0.1.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-KStvlGwnXsqz0wlYSJBfGIM8eHpYeQYjVAoNoVGgaoJKBJ+/MWkxoZZ4PMyL+SWabVJad7cNklKhJd4ZO/MFIg==","shasum":"e7e1b7073f7f76a2c7cd41603bc5f1bed4b24d8f","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioural difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useUser`, `useTeam`, `useFundraisingEvent`,\n`useCause`, `useEventCampaigns`) resolve to `[]` / `null` on the\nTwitch path rather than throwing — same lenient semantics as the\nunderlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.1.1.tgz","shasum":"e7e1b7073f7f76a2c7cd41603bc5f1bed4b24d8f","integrity":"sha512-KStvlGwnXsqz0wlYSJBfGIM8eHpYeQYjVAoNoVGgaoJKBJ+/MWkxoZZ4PMyL+SWabVJad7cNklKhJd4ZO/MFIg=="}},"0.1.2":{"name":"@playlive/react-data","version":"0.1.2","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.1.3","@playlive/tiltify-core":"^0.4.9"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-dpDhhZYAkwPZxT5bRAU8PyQNMT3b69q2dpEgLyBbSK5DFBQxfwcBBgeaPPmc211uatFAkHNKlGhNHnVjLCoBYg==","shasum":"0105c85588ba3e6cebb08ef17f9e884b78216b3f","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useUser`, `useTeam`, `useFundraisingEvent`,\n`useCause`, `useEventCampaigns`) resolve to `[]` / `null` on the\nTwitch path rather than throwing — same lenient semantics as the\nunderlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.1.2.tgz","shasum":"0105c85588ba3e6cebb08ef17f9e884b78216b3f","integrity":"sha512-dpDhhZYAkwPZxT5bRAU8PyQNMT3b69q2dpEgLyBbSK5DFBQxfwcBBgeaPPmc211uatFAkHNKlGhNHnVjLCoBYg=="}},"0.1.3":{"name":"@playlive/react-data","version":"0.1.3","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.1.5","@playlive/tiltify-core":"^0.4.10"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-Sz1VwHipb4zhk4XOXafXP2xQHrEbh81xUmC/3px9F3Zd6b6icZfirKXvdQBvBHV3f4wxa5YKs1KOF8u8MjEJ5Q==","shasum":"d1db2405eb08222ceb60f0989784911e395c6459","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useSchedule`         | `TiltifySchedule[]`                              | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the Twitch path rather than throwing — same lenient\nsemantics as the underlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.1.3.tgz","shasum":"d1db2405eb08222ceb60f0989784911e395c6459","integrity":"sha512-Sz1VwHipb4zhk4XOXafXP2xQHrEbh81xUmC/3px9F3Zd6b6icZfirKXvdQBvBHV3f4wxa5YKs1KOF8u8MjEJ5Q=="}},"0.2.0":{"name":"@playlive/react-data","version":"0.2.0","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.2.0","@playlive/tiltify-core":"^0.4.11"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-GiP39xXaVyOHN1zTWcnfPQBDcKm4q0dvhXqjbxcP6wQFmjRN7Z1/TFM+H97reT+IdhUBTmU27YBJE81bR31nlg==","shasum":"df9b4240ace35d1998acf3157c284378ac46f283","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useSchedule`         | `TiltifySchedule[]`                              | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the Twitch path rather than throwing — same lenient\nsemantics as the underlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.2.0.tgz","shasum":"df9b4240ace35d1998acf3157c284378ac46f283","integrity":"sha512-GiP39xXaVyOHN1zTWcnfPQBDcKm4q0dvhXqjbxcP6wQFmjRN7Z1/TFM+H97reT+IdhUBTmU27YBJE81bR31nlg=="}},"0.2.1":{"name":"@playlive/react-data","version":"0.2.1","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.2.0","@playlive/tiltify-core":"^0.4.11"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-1BXAYIQo9hDB4w94JIF/Lf4TLb8R2wGlDAgQfIJMVLj4SufnqEGNOIa4L+d8sfnCt8vlCGKMEwAoMrraw3tygw==","shasum":"767fd51aa4c0b9504fbf3e35ba25cef76709c843","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useSchedule`         | `TiltifySchedule[]`                              | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the Twitch path rather than throwing — same lenient\nsemantics as the underlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.2.1.tgz","shasum":"767fd51aa4c0b9504fbf3e35ba25cef76709c843","integrity":"sha512-1BXAYIQo9hDB4w94JIF/Lf4TLb8R2wGlDAgQfIJMVLj4SufnqEGNOIa4L+d8sfnCt8vlCGKMEwAoMrraw3tygw=="}},"0.2.2":{"name":"@playlive/react-data","version":"0.2.2","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.2.1","@playlive/tiltify-core":"^0.4.11"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-oeP+aZ7i2esds3ZEhxjAATkavSMfWPdph3aLDPu+YdJpKPVDBCkwwK1oYAn7uAajPSBgfn8hnBpr6dHjoXULmQ==","shasum":"e68af52e2d0d6dda8c9604afd46e9259154d619f","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useSchedule`         | `TiltifySchedule[]`                              | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the Twitch path rather than throwing — same lenient\nsemantics as the underlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.2.2.tgz","shasum":"e68af52e2d0d6dda8c9604afd46e9259154d619f","integrity":"sha512-oeP+aZ7i2esds3ZEhxjAATkavSMfWPdph3aLDPu+YdJpKPVDBCkwwK1oYAn7uAajPSBgfn8hnBpr6dHjoXULmQ=="}},"0.2.3":{"name":"@playlive/react-data","version":"0.2.3","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.2.2","@playlive/tiltify-core":"^0.4.11"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-y8s9hkWlUMWUXGi1edi/ngNTOfOWRFfIZTcAVpW41X6NOx3UjInJqeB/Pc9LyuRIR8om7F+nPOY1sLlFprnpnw==","shasum":"d264e77cd287b3b8e6dcf44d4aff601907cae044","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useSchedule`         | `TiltifySchedule[]`                              | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n| `useTiltifyUserCampaigns` | `TiltifyPersonalCampaign[]`                  | `userId` is nullish / `\"null\"` |\n| `useTiltifyUserAndTeamCampaigns` | `(TiltifyPersonalCampaign \\| TiltifyTeamCampaign)[]` | `userId` is nullish / `\"null\"` |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the Twitch path rather than throwing — same lenient\nsemantics as the underlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.2.3.tgz","shasum":"d264e77cd287b3b8e6dcf44d4aff601907cae044","integrity":"sha512-y8s9hkWlUMWUXGi1edi/ngNTOfOWRFfIZTcAVpW41X6NOx3UjInJqeB/Pc9LyuRIR8om7F+nPOY1sLlFprnpnw=="}},"0.2.4":{"name":"@playlive/react-data","version":"0.2.4","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.2.4","@playlive/tiltify-core":"^0.4.11"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-iJjbVmYbDtcVhoAkte2u1ch606sCGu2u7+UqZClhBT1wPknjYpC8TWQaq54ueGypl2mKUeMdACHX4gq1xC8PgA==","shasum":"9ea1fac2e3633c71fc4031a48b28bf88e8df32a5","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same `{ data, error, isLoading,\nisFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data\nbun add -d react\n```\n\n`react` and `@playlive/fundraiser-data` are **peer dependencies**\n(jose-style — consumer brings their own). `@playlive/tiltify-core` is\nalso listed as a peer because the hook types reference Tiltify domain\ntypes; the value imports are stripped at compile time so nothing of\nit ships in this package's bundle.\n\nNo other peer deps. No `react-dom` — these hooks render nothing.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot.\nconfigure({ tiltifyProxyUrl: \"https://proxy.example\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n`@playlive/react-query`. Migration is a single import rewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The only behavioral difference is\nthat TanStack Query adds cache sharing across components, request\ndeduplication, and background refetch-on-focus.\n\n## Subpath exports\n\n| Subpath                       | Description                                                    |\n| ----------------------------- | -------------------------------------------------------------- |\n| `@playlive/react-data`        | Default barrel — every hook + the `useFetch` primitive + types. |\n| `@playlive/react-data/core`   | The `useFetch` primitive only (build your own domain hooks).   |\n| `@playlive/react-data/types`  | Shared `UseFetchResult` + `UseFetchOptions` types.             |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Hooks (PRD §5.14)\n\n| Hook                  | Returns                                          | Disabled when                |\n| --------------------- | ------------------------------------------------ | ---------------------------- |\n| `useCampaign`         | `Tiltify(Campaign \\| PersonalCampaign \\| TeamCampaign) \\| null` | never (always enabled)    |\n| `useFlattenedDonations` | `TiltifyDonation[]`                           | `campaignId` is nullish      |\n| `useMilestones`       | `TiltifyMilestone[]`                             | `campaignId` is nullish      |\n| `useRewards`          | `TiltifyReward[]`                                | `campaignId` is nullish      |\n| `usePolls`            | `TiltifyPoll[]`                                  | `campaignId` is nullish      |\n| `useTargets`          | `TiltifyTarget[]`                                | `campaignId` is nullish      |\n| `useSchedule`         | `TiltifySchedule[]`                              | `campaignId` is nullish      |\n| `useUser`             | `TiltifyUser \\| null`                            | `userSlug` is empty          |\n| `useTeam`             | `TiltifyTeam \\| null`                            | `teamSlug` is empty          |\n| `useFundraisingEvent` | `TiltifyFundraisingEvent \\| null`                | `eventId` is nullish         |\n| `useCause`            | `TiltifyCause \\| null`                           | `causeId` is nullish         |\n| `useEventCampaigns`   | `TiltifyCampaign[]`                              | `eventId` is nullish         |\n| `useTiltifyUserCampaigns` | `TiltifyPersonalCampaign[]`                  | `userId` is nullish / `\"null\"` |\n| `useTiltifyUserAndTeamCampaigns` | `(TiltifyPersonalCampaign \\| TiltifyTeamCampaign)[]` | `userId` is nullish / `\"null\"` |\n\nAll Twitch-only-unsupported entities (`useMilestones`, `useRewards`,\n`usePolls`, `useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the Twitch path rather than throwing — same lenient\nsemantics as the underlying fetchers.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Default | Description                                       |\n| ----------------- | ------- | ------------------------------------------------- |\n| `enabled`         | `true`  | Skip fetching when `false`. Toggling flips state. |\n| `refetchInterval` | —       | Poll every N ms. `0` / negative disables.         |\n| `retry`           | `0`     | Retries on error.                                 |\n| `retryDelay`      | `1000`  | Base delay (ms); exponential backoff + ±25 % jitter. |\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  data: T | undefined;\n  error: Error | null;\n  isLoading: boolean;\n  isFetching: boolean;\n  refetch: () => Promise<void>;\n}\n```\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface. Every endpoint is reached transitively\nthrough `@playlive/fundraiser-data`'s `configure()` — see that\npackage's README for the proxy + Twitch-service URL knobs.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (plus your own\n`tiltifyProxyUrl` + `twitchServiceUrl` overrides) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlays consuming these hooks live under\n`apps/*` once they're scaffolded (phase 10). Until then, see the\nQuick-start snippet above.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To scaffold a new hook\nthat mirrors a fundraiser-data fetcher, run the `add-react-hook`\nagent skill (lands in phase 7 alongside `@playlive/react-query`).\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.2.4.tgz","shasum":"9ea1fac2e3633c71fc4031a48b28bf88e8df32a5","integrity":"sha512-iJjbVmYbDtcVhoAkte2u1ch606sCGu2u7+UqZClhBT1wPknjYpC8TWQaq54ueGypl2mKUeMdACHX4gq1xC8PgA=="}},"0.3.1":{"name":"@playlive/react-data","version":"0.3.1","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.5.3","@playlive/tiltify-core":"^0.4.18"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-02DG/jLU1aUx40I+5yZUrnHwhhW2vtOskAR1aIHhUPd0jvhfA3lQUgFhL3R+HRtd2RAwTNPJb9PWApwdSrlptA==","shasum":"2d3f05476bf78c182482cb9ddce6327d87d5204b","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same\n`{ data, error, isLoading, isPending, isFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data\n```\n\nEvery dependency is a **required peer** (jose-style — the consumer\nbrings its own copy so wire types stay in lockstep). npm 7+ / Bun\nauto-install them, so the one-liner above is usually enough:\n\n| Peer                                             | Range     | Why                                                                     |\n| ------------------------------------------------ | --------- | ----------------------------------------------------------------------- |\n| `react`                                          | `^19.0.0` | `useState` / `useEffect` / `useRef` / `useCallback`.                     |\n| [`@playlive/fundraiser-data`](../fundraiser-data/) | `*`       | Every hook delegates to its fetchers, and you call its `configure()`.   |\n| [`@playlive/tiltify-core`](../tiltify/core/)     | `*`       | Hook return types reference the Tiltify domain types.                    |\n\nTo pin them explicitly:\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data @playlive/tiltify-core react\n```\n\n`@playlive/tiltify-core` is **type-only** here — the value imports are\nerased at compile time, so nothing of it lands in this package's\nbundle. It still has to be installed because `@playlive/fundraiser-data`\nrequires it at runtime anyway.\n\nNo `react-dom` — these hooks render nothing. No other dependencies.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot, before the first render.\nconfigure({ tiltifyProxyUrl: \"https://tiltify-proxy.prod.experience.stjude.org\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n[`@playlive/react-query`](../react-query/). Migration is a single import\nrewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The behavioral differences: TanStack\nQuery adds cache sharing across components, request deduplication, and\nbackground refetch-on-focus — and its tier honours the pass-through\noptions this tier ignores (`staleTime`, `initialData`, `maxPages`,\n`cachingEnabled`). It also ships hooks with no counterpart here\n(`useInfiniteDonations`, `useAlertsQueue`, `useLeaderboard`, the\ndonation-train mutations, …).\n\n## Subpath exports\n\n| Subpath                      | Description                                                                    |\n| ---------------------------- | ------------------------------------------------------------------------------ |\n| `@playlive/react-data`       | Default barrel — every hook, the `useFetch` primitive, the shared types, plus `PACKAGE_NAME` + `KNOWN_URLS`. |\n| `@playlive/react-data/core`  | The `useFetch` primitive + its `Fetcher<T>` type (build your own domain hooks). |\n| `@playlive/react-data/types` | Type-only: `UseFetchResult<T>` + `UseFetchOptions`.                            |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Campaign + Tiltify entity hooks\n\nEvery hook takes `(params, options?)` where `options` is\n[`UseFetchOptions`](#common-options-usefetchoptions), and returns\n[`UseFetchResult<T>`](#result-usefetchresultt).\n\n| Hook                             | Params                                              | `data`                                                          | Auto-disabled when            |\n| -------------------------------- | --------------------------------------------------- | --------------------------------------------------------------- | ----------------------------- |\n| `useCampaign`                    | `{ charityType, id?, slug?, teamUserSlug?, isTeam? }` | `TiltifyCampaign \\| TiltifyPersonalCampaign \\| TiltifyTeamCampaign \\| null` | never (always enabled) |\n| `useCampaigns`                   | `UseCampaignParams[]`                                | `Array<…Campaign \\| null>` — one `Promise.all` batch            | never (always enabled)        |\n| `useFlattenedDonations`          | `{ campaignId, isTeam?, config?, count?, maxPages? }` | `TiltifyDonation[]`                                             | `campaignId` is nullish       |\n| `useMilestones`                  | `{ charityType, campaignId, isTeam? }`               | `TiltifyMilestone[]`                                            | `campaignId` is nullish       |\n| `useRewards`                     | `{ charityType, campaignId, isTeam?, sort? }`        | `TiltifyReward[]`                                               | `campaignId` is nullish       |\n| `usePolls`                       | `{ charityType, campaignId, isTeam? }`               | `TiltifyPoll[]`                                                 | `campaignId` is nullish       |\n| `useTargets`                     | `{ charityType, campaignId, isTeam?, sort? }`        | `TiltifyTarget[]`                                               | `campaignId` is nullish       |\n| `useSchedule`                    | `{ charityType, campaignId, isTeam? }`               | `TiltifySchedule[]`                                             | `campaignId` is nullish       |\n| `useUser`                        | `{ charityType, userSlug }`                          | `TiltifyUser \\| null`                                           | `userSlug` is empty           |\n| `useTeam`                        | `{ charityType, teamSlug }`                          | `TiltifyTeam \\| null`                                           | `teamSlug` is empty           |\n| `useFundraisingEvent`            | `{ charityType, eventId }`                           | `TiltifyFundraisingEvent \\| null`                               | `eventId` is nullish          |\n| `useCause`                       | `{ charityType, causeId }`                           | `TiltifyCause \\| null`                                          | `causeId` is nullish          |\n| `useEventCampaigns`              | `{ charityType, eventId }`                           | `TiltifyCampaign[]`                                             | `eventId` is nullish          |\n| `useTiltifyUserCampaigns`        | `{ userId }` (Tiltify user **UUID**)                 | `TiltifyPersonalCampaign[]`                                     | `userId` nullish / `\"\"` / `\"null\"` |\n| `useTiltifyUserAndTeamCampaigns` | `{ userId }`                                         | `(TiltifyPersonalCampaign \\| TiltifyTeamCampaign)[]`            | `userId` nullish / `\"\"` / `\"null\"` |\n\n`useCampaigns` is **batch-semantic**: one underlying request fires\n`Promise.all(params.map(fetchCampaign))`, so a partial failure fails the\nwhole batch (`error` set, `data` `undefined`). `@playlive/react-query`'s\nricher `useCampaigns` runs one query per row and exposes per-row errors.\n\nTwitch-unsupported entities (`useMilestones`, `useRewards`, `usePolls`,\n`useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the `charityType: \"twitch\"` path rather than throwing —\nsame lenient semantics as the underlying fetchers.\n\n### Play Live first-party service hooks\n\nThese reach UDP services, so the matching URL must be present in\n`configure()` (or come from a\n[`@playlive/fundraiser-data/environments`](../fundraiser-data/) preset).\n\n| Hook                        | Params                        | `data`                       | Default polling | Auto-disabled when                    | Needs config       |\n| --------------------------- | ----------------------------- | ---------------------------- | --------------- | ------------------------------------- | ------------------ |\n| `useScheduleBlockRaised`    | `{ campaignId, start, end }`  | `ScheduleBlockRaised \\| null` | off             | any of `campaignId` / `start` / `end` is nullish | `scheduleApiUrl`   |\n| `useLifetimeRaised`         | `{ username, isTeam? }`       | `number \\| null`             | `60_000` ms     | `username` is nullish                 | `lifetimeApiUrl`   |\n| `usePreviousYearTotals`     | `{ slug, isTeam? }`           | `PreviousYearTotalItem[]`    | `300_000` ms    | `slug` is nullish                     | `lifetimeApiUrl`   |\n| `useGiftsThatGiveMilestones` | `{ goal }` (`GiftsThatGiveMilestoneGoal \\| number`) | `GiftsThatGiveMilestone[]` | off | `goal` is nullish              | `lifetimeApiUrl`   |\n\nCaller `options` are spread **after** the built-in defaults, so passing\n`{ refetchInterval: 0 }` switches polling off and `{ enabled: true }`\noverrides the auto-disable guard.\n\nFor live (WebSocket-fused) versions of the schedule and spotlight\nviews, see [`@playlive/react-pipeline`](../react-pipeline/)'s\n`useCurrentBlockRaised` / `useLiveSchedule`.\n\n### The `useFetch` primitive\n\n```ts\nimport { useFetch } from \"@playlive/react-data/core\";\n\nfunction useDonorSpotlight(campaignId: string | null) {\n  return useFetch(\n    [\"donor-spotlight\", campaignId], // key: any change ⇒ abort in-flight + refetch\n    ({ signal }) => fetchDonorSpotlight({ campaignId: campaignId as string, signal }),\n    { enabled: !!campaignId, refetchInterval: 30_000, retry: 2 },\n  );\n}\n```\n\n| Export        | Kind          | Signature                                                                            |\n| ------------- | ------------- | ------------------------------------------------------------------------------------ |\n| `useFetch`    | React hook    | `useFetch<T>(key: ReadonlyArray<unknown>, fetcher: Fetcher<T>, options?: UseFetchOptions): UseFetchResult<T>` |\n| `Fetcher<T>`  | type          | `(ctx: { signal: AbortSignal }) => Promise<T>`                                        |\n| `PACKAGE_NAME` | const        | `\"@playlive/react-data\"` — runtime version-pinning.                                  |\n| `KNOWN_URLS`  | const         | Frozen, empty. See [Twitch Extension URL disclosure](#twitch-extension-url-disclosure). |\n\nbehavior:\n\n- The `key` array is serialized (`JSON.stringify`) into a single effect\n  dependency — change it and the in-flight request is aborted and\n  re-issued.\n- `enabled: false` aborts any in-flight request and clears\n  `isLoading` / `isFetching`.\n- Polling runs on its own effect so changing `refetchInterval` resets\n  the timer cleanly.\n- Unmount aborts the in-flight request and cancels pending retry\n  backoff timers; a StrictMode double-invoke is guarded by a fetch-id\n  counter, so only the latest run may write state.\n- `AbortError` is treated as cancellation, never surfaced as `error`.\n\nThe domain hooks above wrap `useFetch` but do **not** forward the\n`AbortSignal` into `@playlive/fundraiser-data` — cancellation there\ndiscards the result rather than aborting the HTTP request. Reach for\n`useFetch` directly when true request-level cancellation matters.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Type              | Default | Description                                              |\n| ----------------- | ----------------- | ------- | -------------------------------------------------------- |\n| `enabled`         | `boolean`         | `true`  | Skip fetching when `false`. Toggling to `true` fetches.  |\n| `refetchInterval` | `number \\| false` | —       | Poll every N ms. `false`, `0`, or negative disables.     |\n| `retry`           | `number \\| boolean` | `0`   | Retry count on error. `true` retries indefinitely.        |\n| `retryDelay`      | `number`          | `1000`  | Base backoff (ms): `retryDelay * 2 ** attempt` ±25 % jitter. |\n| `staleTime`       | `number`          | —       | **Pass-through** — honoured only by `@playlive/react-query`. |\n| `initialData`     | `unknown`         | —       | **Pass-through** — honoured only by `@playlive/react-query`. |\n| `maxPages`        | `number`          | —       | **Pass-through** — infinite-query cap in the other tier. |\n| `cachingEnabled`  | `boolean`         | —       | **Pass-through** — cache partitioning in the other tier. |\n\nThe four pass-through keys exist so a route loader can hand the same\noptions object to either tier without conditional-spread hacks.\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  /** Latest resolved data, or `undefined` until the first success. */\n  data: T | undefined;\n  /** Most recent error thrown by the fetcher, or `null` on success. */\n  error: Error | null;\n  /** `true` from mount (or `enabled` flipping true) until the first settle. */\n  isLoading: boolean;\n  /** `true` while neither data nor error exists — mirrors TanStack v5's `isPending`. */\n  isPending: boolean;\n  /** `true` whenever a fetch is in flight: initial load, poll tick, or `refetch()`. */\n  isFetching: boolean;\n  /** Manually trigger a refetch. Cancels any in-flight request. */\n  refetch: () => Promise<void>;\n}\n```\n\n`isPending` stays `true` for a disabled query with no cached data;\n`isFetching` is `true` for a polling tick on already-resolved data.\nThey're orthogonal — branch on `isPending` for \"never loaded\", on\n`isFetching` for a background-refresh spinner.\n\nRun `bun run docs:build` inside this package to emit the full TypeDoc\nsite at `dist/docs/`.\n\n## Upstream spec\n\nNo external API surface of its own. Every endpoint is reached\ntransitively through [`@playlive/fundraiser-data`](../fundraiser-data/)'s\n`configure()` — see that package's README for the proxy, Twitch-service,\nand UDP URL knobs, and for the Tiltify OpenAPI snapshots under\n[`specs/tiltify/`](../../specs/tiltify/).\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (i.e. every URL you\npass to `configure()`, or the matching `ENV_URLS` row) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [docs/twitch-extension-checklist.md](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\n### A complete campaign overlay\n\nConfigure once at boot, then chain hooks off the resolved campaign ID.\nNullish `campaignId` auto-disables the dependent hooks, so there's no\nmanual `enabled` bookkeeping while the campaign is still loading.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { extractCampaignAmounts, getDonorLevel } from \"@playlive/fundraiser-data/projections\";\nimport {\n  useCampaign,\n  useFlattenedDonations,\n  useLifetimeRaised,\n  useMilestones,\n} from \"@playlive/react-data\";\n\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  lifetimeApiUrl: \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n});\n\nexport function CampaignOverlay({\n  teamUserSlug,\n  slug,\n}: {\n  teamUserSlug: string;\n  slug: string;\n}) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", teamUserSlug, slug },\n    { refetchInterval: 15_000, retry: 2 },\n  );\n\n  // `undefined` until the campaign resolves — the hooks below stay\n  // disabled (and `isPending`) until then.\n  const campaignId = campaign.data?.id;\n\n  const milestones = useMilestones(\n    { charityType: \"tiltify\", campaignId },\n    { refetchInterval: 60_000 },\n  );\n  const donations = useFlattenedDonations(\n    // One page of 25 keeps a long-running campaign from walking its\n    // full donation history on every poll tick.\n    { campaignId, count: 25, maxPages: 1 },\n    { refetchInterval: 10_000 },\n  );\n  const lifetime = useLifetimeRaised({ username: teamUserSlug });\n\n  if (campaign.isPending) return <p className=\"overlay-status\">Loading campaign…</p>;\n  if (campaign.error) return <p className=\"overlay-error\">{campaign.error.message}</p>;\n  if (!campaign.data) return <p className=\"overlay-status\">Campaign not found.</p>;\n\n  const { currentAmount, goalAmount } = extractCampaignAmounts(campaign.data);\n  const nextMilestone = (milestones.data ?? [])\n    .filter((m) => m.active)\n    .sort((a, b) => Number.parseFloat(a.amount.value) - Number.parseFloat(b.amount.value))\n    .find((m) => Number.parseFloat(m.amount.value) > currentAmount);\n\n  return (\n    <section className=\"overlay\" data-fetching={campaign.isFetching}>\n      <h1>{campaign.data.name}</h1>\n\n      <progress value={currentAmount} max={goalAmount || 1} />\n      <p>\n        ${currentAmount.toLocaleString()} of ${goalAmount.toLocaleString()}\n        {lifetime.data !== null && lifetime.data !== undefined ? (\n          <em> · ${lifetime.data.toLocaleString()} lifetime</em>\n        ) : null}\n      </p>\n\n      {nextMilestone ? <p>Next up: {nextMilestone.name} @ ${nextMilestone.amount.value}</p> : null}\n\n      {/* Children fail independently — the overlay stays on screen. */}\n      {donations.error ? (\n        <p className=\"overlay-error\">Donations unavailable: {donations.error.message}</p>\n      ) : (\n        <ul>\n          {(donations.data ?? []).slice(0, 5).map((d) => (\n            <li key={d.id} data-level={getDonorLevel(d.amount.value)}>\n              {d.donor_name} — ${d.amount.value}\n            </li>\n          ))}\n        </ul>\n      )}\n\n      <button type=\"button\" onClick={() => void campaign.refetch()} disabled={campaign.isFetching}>\n        {campaign.isFetching ? \"Refreshing…\" : \"Refresh\"}\n      </button>\n    </section>\n  );\n}\n```\n\n### A campaign picker (Tiltify OAuth flow)\n\n`useTiltifyUserAndTeamCampaigns` guards against the literal `\"null\"`\nstring, which is what a stale `localStorage.getItem(\"userId\")` hands\nback — so the hook stays disabled instead of firing a doomed request.\n\n```tsx\nimport { useTiltifyUserAndTeamCampaigns } from \"@playlive/react-data\";\n\nexport function CampaignPicker({\n  userId,\n  onPick,\n}: {\n  userId: string | null;\n  onPick: (campaignId: string) => void;\n}) {\n  const { data, error, isPending, isFetching, refetch } = useTiltifyUserAndTeamCampaigns(\n    { userId },\n    { retry: 3, retryDelay: 500 },\n  );\n\n  if (!userId) return <p>Sign in with Tiltify to pick a campaign.</p>;\n  if (isPending) return <p>Loading your campaigns…</p>;\n  if (error)\n    return (\n      <p>\n        Couldn’t load campaigns: {error.message}{\" \"}\n        <button type=\"button\" onClick={() => void refetch()}>\n          Retry\n        </button>\n      </p>\n    );\n\n  const campaigns = data ?? [];\n  if (campaigns.length === 0) return <p>No campaigns found for this account.</p>;\n\n  return (\n    <select\n      defaultValue=\"\"\n      disabled={isFetching}\n      onChange={(e) => onPick(e.currentTarget.value)}\n    >\n      <option value=\"\" disabled>\n        Choose a campaign…\n      </option>\n      {campaigns.map((c) => (\n        <option key={c.id} value={c.id}>\n          {c.name}\n        </option>\n      ))}\n    </select>\n  );\n}\n```\n\n### Testing hooks without a test library\n\nThe suites in `tests/` drive a hand-rolled `renderHook` helper\n(`tests/render-hook.ts`) — no `@testing-library/react`, no jsdom. The\nintegration suite boots an in-process GreenRoom and asserts against the\nseeded demo campaign:\n\n```ts\nimport { configure, resetConfig } from \"@playlive/fundraiser-data\";\nimport { useCampaign } from \"@playlive/react-data\";\nimport { actAsync, cleanup, renderHook } from \"./render-hook.ts\";\n\nconfigure({ tiltifyProxyUrl: \"https://v5api.tiltify.com/api/\", causeId: \"demo-cause-st-jude\" });\n\nconst { result } = renderHook(() => useCampaign({ charityType: \"tiltify\", id: \"demo-campaign-a\" }));\nexpect(result.current.isLoading).toBe(true);\n\nawait actAsync(async () => {\n  for (let i = 0; i < 50 && result.current.isLoading; i++) {\n    await new Promise((r) => setTimeout(r, 20));\n  }\n});\n\nexpect(result.current.data).not.toBeNull();\ncleanup();\nresetConfig();\n```\n\nTo scaffold a whole overlay app wired against these hooks, use the\n`/overlay:scaffold` agent skill in\n[`.agents/skills/`](../../.agents/skills/).\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). When adding a hook:\n\n1. Add it to `src/hooks/index.ts` as a thin `useFetch` wrapper — build\n   a stable key array, delegate to the matching\n   `@playlive/fundraiser-data` fetcher, and spread caller `options`\n   last so they win over the defaults.\n2. Mirror it in [`@playlive/react-query`](../react-query/) with the same\n   name, params, and result shape — the two tiers are contractually\n   interchangeable, and `packages/react-query`'s test suite asserts\n   parity one-for-one.\n3. Cover the enabled path, the auto-disabled path, and the key-change\n   refetch in `tests/unit/hooks.test.ts`.\n4. Append the hook to the API reference table above and to the\n   CHANGELOG.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.3.1.tgz","shasum":"2d3f05476bf78c182482cb9ddce6327d87d5204b","integrity":"sha512-02DG/jLU1aUx40I+5yZUrnHwhhW2vtOskAR1aIHhUPd0jvhfA3lQUgFhL3R+HRtd2RAwTNPJb9PWApwdSrlptA=="}},"0.3.2":{"name":"@playlive/react-data","version":"0.3.2","description":"Minimal React hooks over @playlive/fundraiser-data — no TanStack Query, no Zustand. Twitch-Extension safe.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./core":{"import":"./core/index.js","types":"./core/index.d.ts"},"./types":{"import":"./types/index.js","types":"./types/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","@playlive/fundraiser-data":"^0.5.4","@playlive/tiltify-core":"^0.4.19"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-ouuyP0D6UckcX4ta5g3rQDPb5N2uT1AHMRL7MBf8CGVc9EKZwM2zUTcB3KCLoIzpUNzpWbisCtMSvtEA5HmvDQ==","shasum":"fe53c247d142e7d91e269754f6cf415f82bd239e","readme":"# @playlive/react-data\n\nMinimal React hooks over [`@playlive/fundraiser-data`](../fundraiser-data/). Drop-in\ncompatible with [`@playlive/react-query`](../react-query/) — same hook\nnames, same parameter shape, same\n`{ data, error, isLoading, isPending, isFetching, refetch }` return.\n\n**No TanStack Query, no Zustand, no `react-use-websocket-lite`.** Built\non `useState` + `useEffect` + `AbortController` with optional polling\nand exponential-backoff retry. Twitch-Extension safe.\n\n![Coverage](./coverage-badge.svg)\n\n## Install\n\n```bash\nbun add @playlive/react-data\n```\n\nEvery dependency is a **required peer** (jose-style — the consumer\nbrings its own copy so wire types stay in lockstep). npm 7+ / Bun\nauto-install them, so the one-liner above is usually enough:\n\n| Peer                                             | Range     | Why                                                                     |\n| ------------------------------------------------ | --------- | ----------------------------------------------------------------------- |\n| `react`                                          | `^19.0.0` | `useState` / `useEffect` / `useRef` / `useCallback`.                     |\n| [`@playlive/fundraiser-data`](../fundraiser-data/) | `*`       | Every hook delegates to its fetchers, and you call its `configure()`.   |\n| [`@playlive/tiltify-core`](../tiltify/core/)     | `*`       | Hook return types reference the Tiltify domain types.                    |\n\nTo pin them explicitly:\n\n```bash\nbun add @playlive/react-data @playlive/fundraiser-data @playlive/tiltify-core react\n```\n\n`@playlive/tiltify-core` is **type-only** here — the value imports are\nerased at compile time, so nothing of it lands in this package's\nbundle. It still has to be installed because `@playlive/fundraiser-data`\nrequires it at runtime anyway.\n\nNo `react-dom` — these hooks render nothing. No other dependencies.\n\n## Quick start\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { useCampaign, useFlattenedDonations, useMilestones } from \"@playlive/react-data\";\n\n// Configure fundraiser-data once at app boot, before the first render.\nconfigure({ tiltifyProxyUrl: \"https://tiltify-proxy.prod.experience.stjude.org\" });\n\nfunction Overlay({ id }: { id: string }) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", id },\n    { refetchInterval: 5_000 },\n  );\n  const donations = useFlattenedDonations({ campaignId: id });\n  const milestones = useMilestones({ charityType: \"tiltify\", campaignId: id });\n\n  if (campaign.isLoading) return <p>Loading…</p>;\n  if (campaign.error) return <p>Error: {campaign.error.message}</p>;\n\n  return (\n    <pre>\n      {JSON.stringify(\n        { campaign: campaign.data, donations: donations.data, milestones: milestones.data },\n        null,\n        2,\n      )}\n    </pre>\n  );\n}\n```\n\n### Swapping in TanStack Query later\n\nEvery hook in this package has an API-compatible counterpart in\n[`@playlive/react-query`](../react-query/). Migration is a single import\nrewrite:\n\n```diff\n-import { useCampaign } from \"@playlive/react-data\";\n+import { useCampaign } from \"@playlive/react-query\";\n```\n\nSame params. Same return shape. The behavioral differences: TanStack\nQuery adds cache sharing across components, request deduplication, and\nbackground refetch-on-focus — and its tier honours the pass-through\noptions this tier ignores (`staleTime`, `initialData`, `maxPages`,\n`cachingEnabled`). It also ships hooks with no counterpart here\n(`useInfiniteDonations`, `useAlertsQueue`, `useLeaderboard`, the\ndonation-train mutations, …).\n\n## Subpath exports\n\n| Subpath                      | Description                                                                    |\n| ---------------------------- | ------------------------------------------------------------------------------ |\n| `@playlive/react-data`       | Default barrel — every hook, the `useFetch` primitive, the shared types, plus `PACKAGE_NAME` + `KNOWN_URLS`. |\n| `@playlive/react-data/core`  | The `useFetch` primitive + its `Fetcher<T>` type (build your own domain hooks). |\n| `@playlive/react-data/types` | Type-only: `UseFetchResult<T>` + `UseFetchOptions`.                            |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Tree-shaking removes unused exports from the consumer's\nbundle.\n\n## API reference\n\n### Campaign + Tiltify entity hooks\n\nEvery hook takes `(params, options?)` where `options` is\n[`UseFetchOptions`](#common-options-usefetchoptions), and returns\n[`UseFetchResult<T>`](#result-usefetchresultt).\n\n| Hook                             | Params                                              | `data`                                                          | Auto-disabled when            |\n| -------------------------------- | --------------------------------------------------- | --------------------------------------------------------------- | ----------------------------- |\n| `useCampaign`                    | `{ charityType, id?, slug?, teamUserSlug?, isTeam? }` | `TiltifyCampaign \\| TiltifyPersonalCampaign \\| TiltifyTeamCampaign \\| null` | never (always enabled) |\n| `useCampaigns`                   | `UseCampaignParams[]`                                | `Array<…Campaign \\| null>` — one `Promise.all` batch            | never (always enabled)        |\n| `useFlattenedDonations`          | `{ campaignId, isTeam?, config?, count?, maxPages? }` | `TiltifyDonation[]`                                             | `campaignId` is nullish       |\n| `useMilestones`                  | `{ charityType, campaignId, isTeam? }`               | `TiltifyMilestone[]`                                            | `campaignId` is nullish       |\n| `useRewards`                     | `{ charityType, campaignId, isTeam?, sort? }`        | `TiltifyReward[]`                                               | `campaignId` is nullish       |\n| `usePolls`                       | `{ charityType, campaignId, isTeam? }`               | `TiltifyPoll[]`                                                 | `campaignId` is nullish       |\n| `useTargets`                     | `{ charityType, campaignId, isTeam?, sort? }`        | `TiltifyTarget[]`                                               | `campaignId` is nullish       |\n| `useSchedule`                    | `{ charityType, campaignId, isTeam? }`               | `TiltifySchedule[]`                                             | `campaignId` is nullish       |\n| `useUser`                        | `{ charityType, userSlug }`                          | `TiltifyUser \\| null`                                           | `userSlug` is empty           |\n| `useTeam`                        | `{ charityType, teamSlug }`                          | `TiltifyTeam \\| null`                                           | `teamSlug` is empty           |\n| `useFundraisingEvent`            | `{ charityType, eventId }`                           | `TiltifyFundraisingEvent \\| null`                               | `eventId` is nullish          |\n| `useCause`                       | `{ charityType, causeId }`                           | `TiltifyCause \\| null`                                          | `causeId` is nullish          |\n| `useEventCampaigns`              | `{ charityType, eventId }`                           | `TiltifyCampaign[]`                                             | `eventId` is nullish          |\n| `useTiltifyUserCampaigns`        | `{ userId }` (Tiltify user **UUID**)                 | `TiltifyPersonalCampaign[]`                                     | `userId` nullish / `\"\"` / `\"null\"` |\n| `useTiltifyUserAndTeamCampaigns` | `{ userId }`                                         | `(TiltifyPersonalCampaign \\| TiltifyTeamCampaign)[]`            | `userId` nullish / `\"\"` / `\"null\"` |\n\n`useCampaigns` is **batch-semantic**: one underlying request fires\n`Promise.all(params.map(fetchCampaign))`, so a partial failure fails the\nwhole batch (`error` set, `data` `undefined`). `@playlive/react-query`'s\nricher `useCampaigns` runs one query per row and exposes per-row errors.\n\nTwitch-unsupported entities (`useMilestones`, `useRewards`, `usePolls`,\n`useTargets`, `useSchedule`, `useUser`, `useTeam`,\n`useFundraisingEvent`, `useCause`, `useEventCampaigns`) resolve to `[]`\n/ `null` on the `charityType: \"twitch\"` path rather than throwing —\nsame lenient semantics as the underlying fetchers.\n\n### Play Live first-party service hooks\n\nThese reach the Play Live first-party services, so the matching URL must be present in\n`configure()` (or come from a\n[`@playlive/fundraiser-data/environments`](../fundraiser-data/) preset).\n\n| Hook                        | Params                        | `data`                       | Default polling | Auto-disabled when                    | Needs config       |\n| --------------------------- | ----------------------------- | ---------------------------- | --------------- | ------------------------------------- | ------------------ |\n| `useScheduleBlockRaised`    | `{ campaignId, start, end }`  | `ScheduleBlockRaised \\| null` | off             | any of `campaignId` / `start` / `end` is nullish | `scheduleApiUrl`   |\n| `useLifetimeRaised`         | `{ username, isTeam? }`       | `number \\| null`             | `60_000` ms     | `username` is nullish                 | `lifetimeApiUrl`   |\n| `usePreviousYearTotals`     | `{ slug, isTeam? }`           | `PreviousYearTotalItem[]`    | `300_000` ms    | `slug` is nullish                     | `lifetimeApiUrl`   |\n| `useGiftsThatGiveMilestones` | `{ goal }` (`GiftsThatGiveMilestoneGoal \\| number`) | `GiftsThatGiveMilestone[]` | off | `goal` is nullish              | `lifetimeApiUrl`   |\n\nCaller `options` are spread **after** the built-in defaults, so passing\n`{ refetchInterval: 0 }` switches polling off and `{ enabled: true }`\noverrides the auto-disable guard.\n\nFor live (WebSocket-fused) versions of the schedule and spotlight\nviews, see [`@playlive/react-pipeline`](../react-pipeline/)'s\n`useCurrentBlockRaised` / `useLiveSchedule`.\n\n### The `useFetch` primitive\n\n```ts\nimport { useFetch } from \"@playlive/react-data/core\";\n\nfunction useDonorSpotlight(campaignId: string | null) {\n  return useFetch(\n    [\"donor-spotlight\", campaignId], // key: any change ⇒ abort in-flight + refetch\n    ({ signal }) => fetchDonorSpotlight({ campaignId: campaignId as string, signal }),\n    { enabled: !!campaignId, refetchInterval: 30_000, retry: 2 },\n  );\n}\n```\n\n| Export        | Kind          | Signature                                                                            |\n| ------------- | ------------- | ------------------------------------------------------------------------------------ |\n| `useFetch`    | React hook    | `useFetch<T>(key: ReadonlyArray<unknown>, fetcher: Fetcher<T>, options?: UseFetchOptions): UseFetchResult<T>` |\n| `Fetcher<T>`  | type          | `(ctx: { signal: AbortSignal }) => Promise<T>`                                        |\n| `PACKAGE_NAME` | const        | `\"@playlive/react-data\"` — runtime version-pinning.                                  |\n| `KNOWN_URLS`  | const         | Frozen, empty. See [Twitch Extension URL disclosure](#twitch-extension-url-disclosure). |\n\nbehavior:\n\n- The `key` array is serialized (`JSON.stringify`) into a single effect\n  dependency — change it and the in-flight request is aborted and\n  re-issued.\n- `enabled: false` aborts any in-flight request and clears\n  `isLoading` / `isFetching`.\n- Polling runs on its own effect so changing `refetchInterval` resets\n  the timer cleanly.\n- Unmount aborts the in-flight request and cancels pending retry\n  backoff timers; a StrictMode double-invoke is guarded by a fetch-id\n  counter, so only the latest run may write state.\n- `AbortError` is treated as cancellation, never surfaced as `error`.\n\nThe domain hooks above wrap `useFetch` but do **not** forward the\n`AbortSignal` into `@playlive/fundraiser-data` — cancellation there\ndiscards the result rather than aborting the HTTP request. Reach for\n`useFetch` directly when true request-level cancellation matters.\n\n### Common options (`UseFetchOptions`)\n\n| Option            | Type              | Default | Description                                              |\n| ----------------- | ----------------- | ------- | -------------------------------------------------------- |\n| `enabled`         | `boolean`         | `true`  | Skip fetching when `false`. Toggling to `true` fetches.  |\n| `refetchInterval` | `number \\| false` | —       | Poll every N ms. `false`, `0`, or negative disables.     |\n| `retry`           | `number \\| boolean` | `0`   | Retry count on error. `true` retries indefinitely.        |\n| `retryDelay`      | `number`          | `1000`  | Base backoff (ms): `retryDelay * 2 ** attempt` ±25 % jitter. |\n| `staleTime`       | `number`          | —       | **Pass-through** — honoured only by `@playlive/react-query`. |\n| `initialData`     | `unknown`         | —       | **Pass-through** — honoured only by `@playlive/react-query`. |\n| `maxPages`        | `number`          | —       | **Pass-through** — infinite-query cap in the other tier. |\n| `cachingEnabled`  | `boolean`         | —       | **Pass-through** — cache partitioning in the other tier. |\n\nThe four pass-through keys exist so a route loader can hand the same\noptions object to either tier without conditional-spread hacks.\n\n### Result (`UseFetchResult<T>`)\n\n```ts\n{\n  /** Latest resolved data, or `undefined` until the first success. */\n  data: T | undefined;\n  /** Most recent error thrown by the fetcher, or `null` on success. */\n  error: Error | null;\n  /** `true` from mount (or `enabled` flipping true) until the first settle. */\n  isLoading: boolean;\n  /** `true` while neither data nor error exists — mirrors TanStack v5's `isPending`. */\n  isPending: boolean;\n  /** `true` whenever a fetch is in flight: initial load, poll tick, or `refetch()`. */\n  isFetching: boolean;\n  /** Manually trigger a refetch. Cancels any in-flight request. */\n  refetch: () => Promise<void>;\n}\n```\n\n`isPending` stays `true` for a disabled query with no cached data;\n`isFetching` is `true` for a polling tick on already-resolved data.\nThey're orthogonal — branch on `isPending` for \"never loaded\", on\n`isFetching` for a background-refresh spinner.\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-data/docs/>\n\n## Upstream spec\n\nNo external API surface of its own. Every hook delegates to a fetcher in\n[`@playlive/fundraiser-data`](../fundraiser-data/), so the endpoints,\nauthentication, and URL knobs are entirely that package's — configure it\nonce at boot with `configure()` and these hooks inherit whatever you\npointed it at (the Tiltify proxy, the Twitch charity service, and the\nPlay Live schedule / lifetime-raised / leaderboard services). See that\npackage's README for the full list.\n\n## Twitch Extension URL disclosure\n\nThe `KNOWN_URLS` export enumerates every absolute URL or host this\npackage can fetch. **It is empty.** This package doesn't hardcode any\nproduction hosts — every endpoint is reached transitively through\n`@playlive/fundraiser-data`. Add that package's URLs (i.e. every URL you\npass to `configure()`, or the matching `ENV_URLS` row) to your Extension\nsubmission's URL disclosure list.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-data\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### A complete campaign overlay\n\nConfigure once at boot, then chain hooks off the resolved campaign ID.\nNullish `campaignId` auto-disables the dependent hooks, so there's no\nmanual `enabled` bookkeeping while the campaign is still loading.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { extractCampaignAmounts, getDonorLevel } from \"@playlive/fundraiser-data/projections\";\nimport {\n  useCampaign,\n  useFlattenedDonations,\n  useLifetimeRaised,\n  useMilestones,\n} from \"@playlive/react-data\";\n\nconfigure({\n  tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL,\n  lifetimeApiUrl: \"https://main.playlive.lifetime-raised.api.experience.stjude.org\",\n});\n\nexport function CampaignOverlay({\n  teamUserSlug,\n  slug,\n}: {\n  teamUserSlug: string;\n  slug: string;\n}) {\n  const campaign = useCampaign(\n    { charityType: \"tiltify\", teamUserSlug, slug },\n    { refetchInterval: 15_000, retry: 2 },\n  );\n\n  // `undefined` until the campaign resolves — the hooks below stay\n  // disabled (and `isPending`) until then.\n  const campaignId = campaign.data?.id;\n\n  const milestones = useMilestones(\n    { charityType: \"tiltify\", campaignId },\n    { refetchInterval: 60_000 },\n  );\n  const donations = useFlattenedDonations(\n    // One page of 25 keeps a long-running campaign from walking its\n    // full donation history on every poll tick.\n    { campaignId, count: 25, maxPages: 1 },\n    { refetchInterval: 10_000 },\n  );\n  const lifetime = useLifetimeRaised({ username: teamUserSlug });\n\n  if (campaign.isPending) return <p className=\"overlay-status\">Loading campaign…</p>;\n  if (campaign.error) return <p className=\"overlay-error\">{campaign.error.message}</p>;\n  if (!campaign.data) return <p className=\"overlay-status\">Campaign not found.</p>;\n\n  const { currentAmount, goalAmount } = extractCampaignAmounts(campaign.data);\n  const nextMilestone = (milestones.data ?? [])\n    .filter((m) => m.active)\n    .sort((a, b) => Number.parseFloat(a.amount.value) - Number.parseFloat(b.amount.value))\n    .find((m) => Number.parseFloat(m.amount.value) > currentAmount);\n\n  return (\n    <section className=\"overlay\" data-fetching={campaign.isFetching}>\n      <h1>{campaign.data.name}</h1>\n\n      <progress value={currentAmount} max={goalAmount || 1} />\n      <p>\n        ${currentAmount.toLocaleString()} of ${goalAmount.toLocaleString()}\n        {lifetime.data !== null && lifetime.data !== undefined ? (\n          <em> · ${lifetime.data.toLocaleString()} lifetime</em>\n        ) : null}\n      </p>\n\n      {nextMilestone ? <p>Next up: {nextMilestone.name} @ ${nextMilestone.amount.value}</p> : null}\n\n      {/* Children fail independently — the overlay stays on screen. */}\n      {donations.error ? (\n        <p className=\"overlay-error\">Donations unavailable: {donations.error.message}</p>\n      ) : (\n        <ul>\n          {(donations.data ?? []).slice(0, 5).map((d) => (\n            <li key={d.id} data-level={getDonorLevel(d.amount.value)}>\n              {d.donor_name} — ${d.amount.value}\n            </li>\n          ))}\n        </ul>\n      )}\n\n      <button type=\"button\" onClick={() => void campaign.refetch()} disabled={campaign.isFetching}>\n        {campaign.isFetching ? \"Refreshing…\" : \"Refresh\"}\n      </button>\n    </section>\n  );\n}\n```\n\n### A campaign picker (Tiltify OAuth flow)\n\n`useTiltifyUserAndTeamCampaigns` guards against the literal `\"null\"`\nstring, which is what a stale `localStorage.getItem(\"userId\")` hands\nback — so the hook stays disabled instead of firing a doomed request.\n\n```tsx\nimport { useTiltifyUserAndTeamCampaigns } from \"@playlive/react-data\";\n\nexport function CampaignPicker({\n  userId,\n  onPick,\n}: {\n  userId: string | null;\n  onPick: (campaignId: string) => void;\n}) {\n  const { data, error, isPending, isFetching, refetch } = useTiltifyUserAndTeamCampaigns(\n    { userId },\n    { retry: 3, retryDelay: 500 },\n  );\n\n  if (!userId) return <p>Sign in with Tiltify to pick a campaign.</p>;\n  if (isPending) return <p>Loading your campaigns…</p>;\n  if (error)\n    return (\n      <p>\n        Couldn’t load campaigns: {error.message}{\" \"}\n        <button type=\"button\" onClick={() => void refetch()}>\n          Retry\n        </button>\n      </p>\n    );\n\n  const campaigns = data ?? [];\n  if (campaigns.length === 0) return <p>No campaigns found for this account.</p>;\n\n  return (\n    <select\n      defaultValue=\"\"\n      disabled={isFetching}\n      onChange={(e) => onPick(e.currentTarget.value)}\n    >\n      <option value=\"\" disabled>\n        Choose a campaign…\n      </option>\n      {campaigns.map((c) => (\n        <option key={c.id} value={c.id}>\n          {c.name}\n        </option>\n      ))}\n    </select>\n  );\n}\n```\n\n### Testing hooks without a test library\n\nThese hooks need nothing more than React itself, so a ~30-line\nhand-rolled `renderHook` helper is enough to test them — no\n`@testing-library/react`, no jsdom. Point `configure()` at a stub server\n(or register a demo provider) and poll the result:\n\n```ts\nimport { configure, resetConfig } from \"@playlive/fundraiser-data\";\nimport { useCampaign } from \"@playlive/react-data\";\nimport { actAsync, cleanup, renderHook } from \"./render-hook.ts\";\n\nconfigure({ tiltifyProxyUrl: \"https://v5api.tiltify.com/api/\", causeId: \"demo-cause-st-jude\" });\n\nconst { result } = renderHook(() => useCampaign({ charityType: \"tiltify\", id: \"demo-campaign-a\" }));\nexpect(result.current.isLoading).toBe(true);\n\nawait actAsync(async () => {\n  for (let i = 0; i < 50 && result.current.isLoading; i++) {\n    await new Promise((r) => setTimeout(r, 20));\n  }\n});\n\nexpect(result.current.data).not.toBeNull();\ncleanup();\nresetConfig();\n```\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-data/-/react-data-0.3.2.tgz","shasum":"fe53c247d142e7d91e269754f6cf415f82bd239e","integrity":"sha512-ouuyP0D6UckcX4ta5g3rQDPb5N2uT1AHMRL7MBf8CGVc9EKZwM2zUTcB3KCLoIzpUNzpWbisCtMSvtEA5HmvDQ=="}}},"time":{"0.3.0":"2026-08-26T18:10:01.085Z","modified":"2026-08-26T20:09:16.774Z","0.1.0":"2026-08-26T18:15:07.495Z","0.1.1":"2026-08-26T18:15:07.953Z","0.1.2":"2026-08-26T18:15:08.433Z","0.1.3":"2026-08-26T18:15:08.913Z","0.2.0":"2026-08-26T18:15:09.467Z","0.2.1":"2026-08-26T18:15:09.937Z","0.2.2":"2026-08-26T18:15:10.517Z","0.2.3":"2026-08-26T18:15:11.137Z","0.2.4":"2026-08-26T18:15:11.559Z","0.3.1":"2026-08-26T19:45:21.778Z","0.3.2":"2026-08-26T20:09:16.774Z"}}