{
  "markdown": "# react-incremental-funnel\n\nTypeScript-first React package for building incremental funnel flows with a small runtime API and exported types.\n\n## Installation\n\n```bash\nnpm install react-incremental-funnel\n```\n\n## Vite example applications\n\nThis repository includes runnable Vite + React examples:\n\n```text\nexamples/\n  basic-vite/\n  api-backed-vite/\n```\n\nThese examples use generic mock data only (no Good Life Sorted endpoints, schemas, field names, credentials, or business logic).\n\n### Run `basic-vite`\n\n```bash\ncd examples/basic-vite\nnpm install\nnpm run dev\n```\n\n`basic-vite` demonstrates:\n\n- `useIncrementalFunnel` initialization\n- step navigation and step completion/incompletion\n- field persistence policies (`local`, `session`, `memory`)\n- per-field TTL expiry\n- resume/start-again UX\n- submit lifecycle with a mock client-side submit handler\n\n### Run `api-backed-vite`\n\nIn terminal 1:\n\n```bash\ncd examples/api-backed-vite\nnpm install\nnpm run dev:api\n```\n\nIn terminal 2:\n\n```bash\ncd examples/api-backed-vite\nnpm run dev\n```\n\n`api-backed-vite` demonstrates:\n\n- remote session creation (`createSession`)\n- debounced draft updates (`debounceMs` + `updateRemote`)\n- remote submit behavior (`submitRemote`)\n- sync status and error handling (`remoteSyncStatus`, retry)\n- reset/start-again behavior\n- local state clearing after submit\n- `remoteOnly` fields sent to the mock API but not persisted in browser storage\n\n### Mock API behavior (`api-backed-vite/mock-server.js`)\n\nThe mock API stores drafts in memory for demo purposes and exposes generic endpoints:\n\n- `POST /api/drafts` create a draft session\n- `PATCH /api/drafts/:draftId` update a draft\n- `POST /api/drafts/:draftId/submit` submit a draft\n\nIt returns non-sensitive draft metadata only and blocks updates/submissions after a draft is submitted.\n\n### Shared/public device behavior\n\nBoth examples include a visible resume/start-again prompt:\n\n- “We found a saved request on this device.”\n- “Continue saved request or start again.”\n\nThe prompt does not display sensitive values.\n\nUse `startAgain()` to clear persisted local state and reset funnel values. In API-backed flows, this also starts a new mock draft session.\n\n### Example builds in CI\n\nCI builds both example apps (`npm run build:examples`) so changes that break example integration fail quickly.\n\n## Basic hook usage\n\n```tsx\nimport { useIncrementalFunnel } from 'react-incremental-funnel';\n\ntype FunnelValues = {\n  fullName?: string;\n  email?: string;\n  consent?: boolean;\n};\n\nexport function BasicFunnel() {\n  const funnel = useIncrementalFunnel<FunnelValues>({\n    storageKey: 'example-funnel',\n    steps: ['start', 'details', 'review']\n  });\n\n  return (\n    <button\n      onClick={() => {\n        funnel.updateValues({ consent: true });\n        funnel.nextStep();\n      }}\n    >\n      Continue\n    </button>\n  );\n}\n```\n\n## Example integration (mock endpoints only)\n\n```tsx\nimport { useIncrementalFunnel } from 'react-incremental-funnel';\n\ntype FunnelValues = {\n  fullName?: string;\n  email?: string;\n  consent?: boolean;\n};\n\nconst mockApi = {\n  async createSession() {\n    return { sessionId: 'mock-session-id' };\n  },\n  async saveProgress(values: Partial<FunnelValues>) {\n    await fetch('/mock/funnel/progress', {\n      method: 'POST',\n      headers: { 'content-type': 'application/json' },\n      body: JSON.stringify(values)\n    });\n  },\n  async submit(values: Partial<FunnelValues>) {\n    await fetch('/mock/funnel/submit', {\n      method: 'POST',\n      headers: { 'content-type': 'application/json' },\n      body: JSON.stringify(values)\n    });\n  }\n};\n\nexport function FunnelWithMockApi() {\n  const funnel = useIncrementalFunnel<\n    FunnelValues,\n    'start' | 'details' | 'review'\n  >({\n    storageKey: 'example-funnel',\n    steps: ['start', 'details', 'review'],\n    createSession: () => mockApi.createSession(),\n    updateRemote: values => mockApi.saveProgress(values),\n    submitRemote: values => mockApi.submit(values)\n  });\n\n  return <button onClick={() => void funnel.submit()}>Submit</button>;\n}\n```\n\n## Step orchestration\n\nUse these APIs to control progress through your funnel:\n\n- `nextStep()` / `previousStep()` to move through `steps`\n- `goToStep(stepId)` to jump to a specific step\n- `markStepComplete(stepId)` / `markStepIncomplete(stepId)` for explicit completion state\n- `currentStepId`, `completedStepIds`, `canGoNext`, and `canGoBack` for UI guards\n- `persistStepState: true` to persist step position across sessions\n- `includeStepStateInRemoteUpdate: true` to include step state in remote updates\n\n## Field-level persistence policies\n\nUse `fieldPolicies` to control where each field can persist:\n\n- `local`: persist in local storage\n- `session`: persist in session storage\n- `memory`: persist in memory only\n- `remoteOnly`: never persist locally, include only in remote updates/submission\n\n`ttlMs` can be added per field to expire persisted values automatically.\n\n```ts\nfieldPolicies: {\n  fullName: { persist: 'local', ttlMs: 7 * 24 * 60 * 60 * 1000 },\n  email: { persist: 'session', ttlMs: 2 * 60 * 60 * 1000 },\n  consent: { persist: 'memory' },\n  temporaryInput: { persist: 'memory' },\n  sensitiveDraft: { persist: 'remoteOnly' }\n}\n```\n\n## Storage adapters\n\nBuilt-in adapters:\n\n- `createLocalStorageAdapter()`\n- `createSessionStorageAdapter()`\n- `createMemoryStorageAdapter()`\n\nOverride any adapter with `storageAdapters`:\n\n```ts\nstorageAdapters: {\n  memory: createMemoryStorageAdapter();\n}\n```\n\n## Remote update callbacks\n\nUse `updateRemote(values)` (or `remoteUpdate({ values, stepState })`) to receive debounced in-progress updates.\n\nPair with lifecycle callbacks:\n\n- `onRemoteUpdateSucceeded`\n- `onRemoteUpdateFailed`\n\nInspect `remoteSyncStatus` and `lastSuccessfulRemoteSyncAt` to drive UI status.\n\n## Session creation callbacks\n\nUse `createSession()` to create a server-side draft/session at funnel start.\n\nInspect session state with:\n\n- `sessionCreationStatus`\n- `sessionCreationError`\n- `sessionMetadata`\n\n## Submit callbacks\n\nUse `submitRemote(values)` for final submission and call `submit()` from the hook result.\n\nInspect submit state with:\n\n- `submitStatus`\n- `submitError`\n\nLifecycle callbacks for submission:\n\n- `onSubmitStarted`\n- `onSubmitSucceeded`\n- `onSubmitFailed`\n\n## Resume / start-again handling\n\nUse saved progress flags:\n\n- `savedProgressExists`\n- `savedProgressIsStale`\n- `savedProgressMetadata`\n\nActions:\n\n- `continueSavedProgress()`\n- `startAgain()`\n- `clearSavedProgress()` (removes persisted progress only)\n\n## Validation callback usage\n\nProvide per-step and full-submit validation callbacks:\n\n```ts\nvalidateStep: async (stepId, values) => {\n  if (stepId === 'details' && !values.email) {\n    return {\n      stepErrors: ['Please complete this step'],\n      fieldErrors: { email: 'Email is required' }\n    };\n  }\n},\nvalidateAll: async values => {\n  if (!values.consent) {\n    return {\n      stepErrors: ['Please accept before submitting'],\n      fieldErrors: { consent: 'Consent is required' }\n    };\n  }\n}\n```\n\nUse `canContinueCurrentStep`, `currentStepValidationErrors`, and `fieldValidationErrors` in UI.\n\n## Lifecycle event callbacks\n\nYou can subscribe to lifecycle events:\n\n- `onFunnelStarted`\n- `onStepStarted`\n- `onStepCompleted`\n- `onValuesChanged`\n- `onRemoteUpdateSucceeded`\n- `onRemoteUpdateFailed`\n- `onSubmitStarted`\n- `onSubmitSucceeded`\n- `onSubmitFailed`\n- `onFunnelReset`\n\nSet `includeValuesInLifecycleCallbacks: true` only when you explicitly need values payloads.\n\n## Shared/public device guidance\n\nFor shared/public devices:\n\n- Prefer `session` or `memory` persistence over `local`\n- Use short `ttlMs` values for persisted fields\n- Mark sensitive fields as `memory` or `remoteOnly`\n- Offer a visible “Start again” action that calls `startAgain()`\n- Offer a visible “Clear saved progress” action that calls `clearSavedProgress()`\n\n## Security and privacy guidance\n\n- Do not store secrets in funnel values.\n- Treat local/session storage as user-accessible and non-secret storage.\n- Persist only what is required; default sensitive fields to `memory` or `remoteOnly`.\n- Redact or minimize telemetry in lifecycle callbacks unless required.\n- Validate and sanitize values server-side before trusting updates/submissions.\n\n## Development\n\n```bash\nnpm install\nnpm run lint\nnpm run test\nnpm run build\nnpm run build:examples\n```\n\n## Release workflow\n\nThis package uses [Changesets](https://github.com/changesets/changesets) for versioning and changelogs.\n\n### Add a changeset in your PR\n\nIf your PR changes package behavior, add a changeset:\n\n```bash\nnpm run changeset\n```\n\nChoose the bump type:\n\n- `patch`: bug fixes and other backwards-compatible fixes.\n- `minor`: backwards-compatible features.\n- `major`: breaking changes.\n\n### How releases happen\n\n- Changes merge through pull requests into `main`.\n- On pushes to `main`, the Release workflow runs `changesets/action`.\n- If unreleased changesets exist, it creates or updates a release PR with:\n  - `package.json` version updates\n  - `CHANGELOG.md` updates\n  - consumed changesets removed\n- When that release PR is merged, the same workflow publishes to npm with:\n  - `npm publish --provenance --access public`\n  - GitHub OIDC Trusted Publishing (`id-token: write`) via GitHub Actions\n\nDo not normally run `npm publish` from a developer machine.\n\n### Stable and prerelease channels\n\n- Stable releases are published from `main` to the default `latest` tag (for example `1.1.0`).\n- If prereleases are needed, use Changesets prerelease mode and publish with a prerelease tag such as `next` (for example `1.2.0-next.0`).\n\n### Local package verification\n\nBefore release, verify package contents locally:\n\n```bash\nnpm pack --dry-run\n```\n\n## Public API\n\n- `createFunnel`\n- `advanceFunnel`\n- `useIncrementalFunnel`\n- `createLocalStorageAdapter`, `createSessionStorageAdapter`, `createMemoryStorageAdapter`\n- `pickPersistableValues`, `removeBlockedFields`, `redactValues`\n- `FunnelStep`, `FunnelState`, `UseIncrementalFunnelOptions`, `UseIncrementalFunnelResult`, `FunnelStepId`\n",
  "bytes": 10016,
  "sha": "2cea6d94ddeafda2f5003deef24c5d3111e6d4643e344edcf22aad1b561c0259",
  "repo_slug": "olivercox/react-incremental-funnel",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_olivercox_react_incremental_funnel_wiki__9902e762/readme"
}