{"_id":"@adetolla/react-idempo","name":"@adetolla/react-idempo","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.1":{"name":"@adetolla/react-idempo","version":"1.0.1","description":"A React idempotency helper for preventing duplicate API submissions and making retry-safe requests.","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"build":"tsup","dev":"tsup --watch","playground":"vite --host 0.0.0.0","playground:build":"vite build","test":"echo \"Error: no test specified\" && exit 1"},"keywords":["react","idempotency","api","retry","hooks"],"author":{"name":"Adetola Aremu","email":"aremutola@gmail.com"},"license":"ISC","peerDependencies":{"react":">=16.14.0"},"devDependencies":{"@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@types/uuid":"^10.0.0","@vitejs/plugin-react":"^4.7.0","react":"^19.2.5","react-dom":"^19.2.7","tsup":"^8.5.1","typescript":"^6.0.3","vite":"^6.4.3"},"dependencies":{"uuid":"^10.0.0"},"_id":"@adetolla/react-idempo@1.0.1","gitHead":"6d0d4251ad0c2f278b0a1c2f7a141dd02e6b3078","_nodeVersion":"20.11.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-/+W4qqxyadbGOqU49Ifmcj9Zp8kTEFKbNduZ27yBKzHRtpyuO7kvCmOiIeOgjI/c1vsMgD9Fjism6R5bumrZVA==","shasum":"2afc91972a2dc6cbe5c330412c8d3b893d457aff","tarball":"https://registry.npmjs.org/@adetolla/react-idempo/-/react-idempo-1.0.1.tgz","fileCount":8,"unpackedSize":56031,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCCeb945blwg8RDXxJdG6nbCiSc8WaOK9eo2Dr8rmf1/wIgZM8vcJNO2fQmp8iiMGPuYRPJrvxWEJgEILsEc+W8VwY="}]},"_npmUser":{"name":"adetolla","email":"aremutola@gmail.com"},"directories":{},"maintainers":[{"name":"adetolla","email":"aremutola@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-idempo_1.0.1_1782552633808_0.9716382659284601"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-27T09:30:33.613Z","1.0.1":"2026-06-27T09:30:33.948Z","modified":"2026-06-27T09:30:34.141Z"},"maintainers":[{"name":"adetolla","email":"aremutola@gmail.com"}],"description":"A React idempotency helper for preventing duplicate API submissions and making retry-safe requests.","keywords":["react","idempotency","api","retry","hooks"],"author":{"name":"Adetola Aremu","email":"aremutola@gmail.com"},"license":"ISC","readme":"# @adetolla/react-idempo\n\nA React idempotency helper for preventing duplicate API submissions and making retry-safe requests.\n\n## Features\n\n- **Duplicate Prevention**: Prevents double-clicks and concurrent form submissions while a request is pending.\n- **Idempotency Keys**: Automatically generates, stores, and attaches UUID v4 idempotency keys to your requests.\n- **Retry-Safe**: Reuses the same idempotency key for retries if a request fails (e.g. due to network errors).\n- **Auto-Rotation**: Generates a new key automatically upon a successful request.\n- **Storage Adapters**: Built-in support for `localStorage`, `sessionStorage`, and `cookies`.\n- **TTL Expiry**: Automatically cleans up and rotates expired keys based on a configured Time-To-Live.\n- **Network Adapters**: First-class support and helpers for both `fetch` and `axios`.\n- **TypeScript Ready**: Written in TypeScript with full type safety.\n\n## Installation\n\n```bash\nnpm install @adetolla/react-idempo\n```\nor\n```bash\nyarn add @adetolla/react-idempo\n```\n\n## Quick Start\n\nThe simplest way to use `@adetolla/react-idempo` is with the `useIdempotentSubmit` hook.\n\n```tsx\nimport { useIdempotentSubmit, fetchWithIdempotency } from '@adetolla/react-idempo';\n\nfunction CheckoutForm() {\n  const { submit, isPending } = useIdempotentSubmit({\n    keyName: 'checkout_submit', // unique namespace per form\n    onSubmit: async (key, formData) => {\n      // The key is passed as the first argument.\n      // fetchWithIdempotency automatically attaches it to the 'Idempotency-Key' header.\n      const response = await fetchWithIdempotency('/api/checkout', {\n        method: 'POST',\n        body: JSON.stringify(formData),\n        idempotencyKey: key, \n      });\n      \n      if (!response.ok) throw new Error('Payment failed');\n      return response.json();\n    },\n    onSuccess: (data) => {\n      alert('Payment successful!');\n    },\n    onError: (error) => {\n      alert('Payment failed, but you can retry safely.');\n    }\n  });\n\n  return (\n    <button \n      onClick={() => submit({ amount: 100, currency: 'USD' })} \n      disabled={isPending}\n    >\n      {isPending ? 'Processing...' : 'Pay Now'}\n    </button>\n  );\n}\n```\n\n## Global Configuration (Optional)\n\nYou can wrap your application with the `IdempotencyProvider` to configure global default settings such as the storage mechanism and TTL.\n\n```tsx\nimport { IdempotencyProvider, SessionStorageAdapter } from '@adetolla/react-idempo';\n\nfunction App() {\n  return (\n    <IdempotencyProvider \n      storage={new SessionStorageAdapter()} \n      ttl={60 * 60 * 1000} // 1 hour TTL\n      keyPrefix=\"my_app_idempo_\"\n    >\n      <CheckoutForm />\n    </IdempotencyProvider>\n  );\n}\n```\n\n## Network Adapters\n\n### Fetch Adapter\n`fetchWithIdempotency` is a lightweight wrapper around the native `fetch` API. It automatically adds the `Idempotency-Key` header if the `idempotencyKey` option is provided.\n\n```typescript\nimport { fetchWithIdempotency } from '@adetolla/react-idempo';\n\nfetchWithIdempotency('/api/data', {\n  method: 'POST',\n  idempotencyKey: 'your-uuid-here',\n  headerName: 'X-Idempotency-Key' // Optional: Custom header name\n});\n```\n\n### Axios Adapter\nIf you use Axios, you can use the provided interceptor factory.\n\n```typescript\nimport axios from 'axios';\nimport { createAxiosIdempotencyInterceptor } from '@adetolla/react-idempo';\n\nconst myAxiosInstance = axios.create();\n\n// A simple example assuming you retrieve the key dynamically\nconst myKey = \"123e4567-e89b-12d3-a456-426614174000\";\n\nmyAxiosInstance.interceptors.request.use(\n  createAxiosIdempotencyInterceptor(() => myKey)\n);\n```\n\n## API Reference\n\n### `useIdempotentSubmit(options)`\n\n**Options:**\n- `keyName` (string, optional): The namespace for the storage key. Defaults to `'default'`.\n- `ttl` (number, optional): Time-To-Live in milliseconds.\n- `onSubmit` (function, required): The asynchronous function to execute. Receives the `idempotencyKey` as the first argument, followed by any arguments passed to the returned `submit` function.\n- `onSuccess` (function, optional): Callback executed when `onSubmit` resolves successfully.\n- `onError` (function, optional): Callback executed when `onSubmit` throws an error.\n- `generateNewKeyOnSuccess` (boolean, optional): Whether to automatically rotate the key on success. Defaults to `true`.\n\n**Returns:**\n- `submit`: A function to trigger the submission.\n- `isPending`: A boolean indicating if the submission is currently in progress.\n- `idempotencyKey`: The current idempotency key string.\n\n### `useIdempotencyKey(options)`\n\nA lower-level hook if you need direct access to key management without the submit wrapper.\n\n**Returns:**\n- `idempotencyKey`: The current idempotency key string.\n- `generateKey`: A function to force generation of a new key.\n- `clearKey`: A function to remove the key from storage.\n\n### Storage Adapters\n- `LocalStorageAdapter` (default)\n- `SessionStorageAdapter`\n- `CookieStorageAdapter`\n\nYou can also write your own custom adapter by implementing the `StorageAdapter` interface:\n```typescript\ninterface StorageAdapter {\n  get(key: string): string | null;\n  set(key: string, value: string, ttl?: number): void;\n  remove(key: string): void;\n}\n```\n\n## License\nISC\n","readmeFilename":"README.md","_rev":"1-fed42ac8a9576544fc5021ea7cddba90"}