@hirely/hooks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +503 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/index.cjs +573 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +535 -0
- package/dist/index.mjs +535 -0
- package/dist/useFormHandler.d.ts +61 -0
- package/dist/useFormHandler.d.ts.map +1 -0
- package/dist/useFormMutation.d.ts +26 -0
- package/dist/useFormMutation.d.ts.map +1 -0
- package/dist/withFormHandler.d.ts +12 -0
- package/dist/withFormHandler.d.ts.map +1 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# @hirely/hooks
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@hirely/hooks)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://react.dev/)
|
|
9
|
+
[](https://bun.sh)
|
|
10
|
+
[](https://github.com/hirely/hooks)
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<strong>Production-ready, type-safe React form hook with Zod validation, seamless HTTP integration (Axios/fetch/Server Actions), and toast notifications.</strong>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<em>Zero forced runtime dependencies • Built-in render optimizations • Full React 18 & 19 Support</em>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Highlights
|
|
25
|
+
|
|
26
|
+
- 🛡️ **End-to-End Type Safety** – Define your Zod schema once; get instant compile-time validation and form inference.
|
|
27
|
+
- ⚡ **High Performance & Zero-Render Overhead** – Internal callback ref stabilization guarantees that `onSubmit` and handlers remain referentially stable across re-renders even when passing inline functions.
|
|
28
|
+
- 🧩 **Zero Forced Dependencies** – Works out of the box with custom services, Next.js Server Actions, or native `fetch`. Axios and Sonner are dynamically loaded **only if requested**.
|
|
29
|
+
- 🚀 **Cached Module Loading** – Dynamic imports for optional libraries are cached in memory for sub-millisecond subsequent submissions.
|
|
30
|
+
- 🔄 **TanStack Query v5 Ready** – First-class `useFormMutation` hook combining form management and async mutations.
|
|
31
|
+
- 🌐 **Global Defaults** – Configure once via `createFormHandler` factory or wrap sections with `FormHandlerProvider`.
|
|
32
|
+
- 📦 **Modern Dual ESM/CJS Bundle** – Native ESM (`.mjs`/`.js`), CommonJS (`.cjs`), complete TypeScript definitions (`.d.ts`), and `"use client";` directives for Next.js App Router.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Table of Contents
|
|
37
|
+
|
|
38
|
+
- [Installation](#installation)
|
|
39
|
+
- [Quick Start](#quick-start)
|
|
40
|
+
- [Core API](#core-api)
|
|
41
|
+
- [`useFormHandler`](#useformhandleroptions)
|
|
42
|
+
- [Return Value](#return-value)
|
|
43
|
+
- [Next.js Server Actions](#using-with-nextjs-server-actions)
|
|
44
|
+
- [React Query Integration (`useFormMutation`)](#react-query-integration-useformmutation)
|
|
45
|
+
- [Global Configuration](#global-configuration)
|
|
46
|
+
- [Factory Pattern (`createFormHandler`)](#1-factory-pattern-createformhandler)
|
|
47
|
+
- [React Context (`FormHandlerProvider`)](#2-react-context-formhandlerprovider)
|
|
48
|
+
- [Higher-Order Component (`withFormHandler`)](#higher-order-component-withformhandler)
|
|
49
|
+
- [Recipes & Advanced Usage](#recipes--advanced-usage)
|
|
50
|
+
- [File Uploads (`useFormData`)](#file-uploads-with-formdata)
|
|
51
|
+
- [Request Cancellation (`enableAbort`)](#request-cancellation)
|
|
52
|
+
- [Optimistic Updates](#optimistic-updates)
|
|
53
|
+
- [Native `fetch` Client](#using-native-fetch)
|
|
54
|
+
- [Custom Response & Error Parsers](#custom-response--error-parsers)
|
|
55
|
+
- [License](#license)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Bun
|
|
63
|
+
bun add @hirely/hooks
|
|
64
|
+
|
|
65
|
+
# npm
|
|
66
|
+
npm install @hirely/hooks
|
|
67
|
+
|
|
68
|
+
# pnpm
|
|
69
|
+
pnpm add @hirely/hooks
|
|
70
|
+
|
|
71
|
+
# Yarn
|
|
72
|
+
yarn add @hirely/hooks
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Peer Dependencies
|
|
76
|
+
|
|
77
|
+
Install the core dependencies:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Required
|
|
81
|
+
bun add react react-hook-form zod
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Optional dependencies** (only required if you use their respective features):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# For default HTTP client and toast notifications:
|
|
88
|
+
bun add axios sonner
|
|
89
|
+
|
|
90
|
+
# For TanStack React Query integration:
|
|
91
|
+
bun add @tanstack/react-query
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Note:** If you pass your own async `service` or `notify` handler, you don't even need `axios` or `sonner`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Quick Start
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { useFormHandler } from "@hirely/hooks";
|
|
102
|
+
import { z } from "zod";
|
|
103
|
+
|
|
104
|
+
const loginSchema = z.object({
|
|
105
|
+
email: z.string().email("Invalid email address"),
|
|
106
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export function LoginForm() {
|
|
110
|
+
const { register, onSubmit, loading, error } = useFormHandler({
|
|
111
|
+
schema: loginSchema,
|
|
112
|
+
endpoint: "/api/auth/login",
|
|
113
|
+
onSuccess: (data) => {
|
|
114
|
+
console.log("Logged in successfully:", data);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<form onSubmit={onSubmit} className="form-container">
|
|
120
|
+
<input {...register("email")} type="email" placeholder="Email" />
|
|
121
|
+
<input {...register("password")} type="password" placeholder="Password" />
|
|
122
|
+
|
|
123
|
+
{error && <p className="error-text">{error.message}</p>}
|
|
124
|
+
|
|
125
|
+
<button type="submit" disabled={loading}>
|
|
126
|
+
{loading ? "Signing in..." : "Sign In"}
|
|
127
|
+
</button>
|
|
128
|
+
</form>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Core API
|
|
136
|
+
|
|
137
|
+
### `useFormHandler(options)`
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const form = useFormHandler(options);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Options
|
|
144
|
+
|
|
145
|
+
| Option | Type | Default | Description |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| `schema` | `ZodType<FieldValues>` | *Required* | Zod schema used for form validation and type inference. |
|
|
148
|
+
| `endpoint` | `string` | `undefined` | Target URL (used with Axios if no custom `service` is provided). |
|
|
149
|
+
| `method` | `'post' \| 'patch' \| 'put' \| 'delete'` | `'post'` | HTTP method for `endpoint`. |
|
|
150
|
+
| `service` | `(data: TData) => Promise<any>` | `undefined` | Custom async function (Server Action, `fetch`, SDK). Overrides `endpoint`. |
|
|
151
|
+
| `defaultValues` | `DefaultValues<TData>` | `undefined` | Default values for form fields. |
|
|
152
|
+
| `values` | `TData` | `undefined` | Reactive external values synced to the form. |
|
|
153
|
+
| `transformData` | `(data: TData) => any` | `undefined` | Transform payload prior to submission. |
|
|
154
|
+
| `onMutate` | `(data: TData) => any \| Promise<any>` | `undefined` | Pre-submission hook. Return value is passed as `context` to `onSuccess`/`onError`. |
|
|
155
|
+
| `onSuccess` | `(data: any, context?: any) => void` | `undefined` | Callback invoked after a successful submission. |
|
|
156
|
+
| `onError` | `(error: any, context?: any) => void` | `undefined` | Callback invoked upon submission failure. |
|
|
157
|
+
| `onSubmitStart` | `() => void` | `undefined` | Triggered immediately when submission initiates. |
|
|
158
|
+
| `onSubmitEnd` | `() => void` | `undefined` | Triggered when submission concludes (success or error). |
|
|
159
|
+
| `notify` | `(message: string, type: 'success' \| 'error') => void` | Sonner toast | Custom notification function. Falls back to Sonner if installed. |
|
|
160
|
+
| `resetOptions` | `object` | `{ resetAfterSuccess: true, keepDefaultValues: true }` | Controls form reset behavior on success. |
|
|
161
|
+
| `useFormData` | `boolean` | `false` | Automatically converts payload to `FormData` (supports `File` and `Blob`). |
|
|
162
|
+
| `enableAbort` | `boolean` | `false` | Enables `abort()` method to cancel in-flight HTTP requests. |
|
|
163
|
+
| `axiosConfig` | `Record<string, any>` | `{}` | Additional configuration passed to Axios. |
|
|
164
|
+
| `mode` | `'onSubmit' \| 'onBlur' \| 'onChange' \| 'onTouched' \| 'all'` | `'onSubmit'` | React Hook Form validation mode. |
|
|
165
|
+
| `reValidateMode`| `'onChange' \| 'onBlur' \| 'onSubmit'` | `'onChange'` | Validation mode on re-renders after submit. |
|
|
166
|
+
| `parseResponse` | `(res: any) => { success: boolean, message?: string, data?: any }` | Standard parser | Custom response validator and extractor. |
|
|
167
|
+
| `parseError` | `(err: any) => { message: string }` | Axios error parser | Custom error message extractor. |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### Return Value
|
|
172
|
+
|
|
173
|
+
Returns **everything from React Hook Form's `useForm`** (`register`, `watch`, `setValue`, `getValues`, `formState`, `control`, etc.) along with:
|
|
174
|
+
|
|
175
|
+
| Property | Type | Description |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `onSubmit` | `(e?: unknown) => Promise<void>` | Stable submit handler ready to attach to `<form onSubmit={onSubmit}>`. |
|
|
178
|
+
| `loading` | `boolean` | `true` while the async request or service is active. |
|
|
179
|
+
| `error` | `Error \| null` | Error object if submission failed. |
|
|
180
|
+
| `setError` | `React.Dispatch<React.SetStateAction<Error \| null>>` | Manually set or clear the form error state. |
|
|
181
|
+
| `abort` | `(() => void) \| undefined` | Cancels the active in-flight request (active when `enableAbort: true`). |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Using with Next.js Server Actions
|
|
186
|
+
|
|
187
|
+
Because `service` accepts any standard async function, you can plug in **Next.js Server Actions** directly with full type safety:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
// app/actions/user.ts
|
|
191
|
+
"use server";
|
|
192
|
+
|
|
193
|
+
import { z } from "zod";
|
|
194
|
+
|
|
195
|
+
const createUserSchema = z.object({
|
|
196
|
+
name: z.string().min(2),
|
|
197
|
+
email: z.string().email(),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
export async function createUserAction(data: z.infer<typeof createUserSchema>) {
|
|
201
|
+
// Database or external API call
|
|
202
|
+
return { success: true, message: "Account created!", id: "user_123" };
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
// app/components/SignupForm.tsx
|
|
208
|
+
"use client";
|
|
209
|
+
|
|
210
|
+
import { useFormHandler } from "@hirely/hooks";
|
|
211
|
+
import { createUserAction } from "@/app/actions/user";
|
|
212
|
+
import { z } from "zod";
|
|
213
|
+
|
|
214
|
+
const schema = z.object({
|
|
215
|
+
name: z.string().min(2),
|
|
216
|
+
email: z.string().email(),
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export function SignupForm() {
|
|
220
|
+
const { register, onSubmit, loading } = useFormHandler({
|
|
221
|
+
schema,
|
|
222
|
+
service: createUserAction,
|
|
223
|
+
onSuccess: (data) => console.log("Created user ID:", data.id),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<form onSubmit={onSubmit}>
|
|
228
|
+
<input {...register("name")} placeholder="Your name" />
|
|
229
|
+
<input {...register("email")} placeholder="Your email" />
|
|
230
|
+
<button type="submit" disabled={loading}>
|
|
231
|
+
{loading ? "Creating..." : "Create Account"}
|
|
232
|
+
</button>
|
|
233
|
+
</form>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## React Query Integration (`useFormMutation`)
|
|
241
|
+
|
|
242
|
+
For projects using **@tanstack/react-query**, `useFormMutation` unites form management with React Query's cache invalidation, mutation tracking, and retry logic:
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { useFormMutation } from "@hirely/hooks";
|
|
246
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
247
|
+
import { z } from "zod";
|
|
248
|
+
|
|
249
|
+
const postSchema = z.object({
|
|
250
|
+
title: z.string().min(1),
|
|
251
|
+
content: z.string().min(10),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
export function CreatePost() {
|
|
255
|
+
const queryClient = useQueryClient();
|
|
256
|
+
|
|
257
|
+
const {
|
|
258
|
+
register,
|
|
259
|
+
mutate,
|
|
260
|
+
isPending,
|
|
261
|
+
isLoading, // backward compatible alias for isPending
|
|
262
|
+
error,
|
|
263
|
+
reset, // resets both mutation and form fields
|
|
264
|
+
} = useFormMutation({
|
|
265
|
+
schema: postSchema,
|
|
266
|
+
endpoint: "/api/posts",
|
|
267
|
+
mutationOptions: {
|
|
268
|
+
onSuccess: () => {
|
|
269
|
+
queryClient.invalidateQueries({ queryKey: ["posts"] });
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<form onSubmit={mutate}>
|
|
276
|
+
<input {...register("title")} placeholder="Title" />
|
|
277
|
+
<textarea {...register("content")} placeholder="Content" />
|
|
278
|
+
<button type="submit" disabled={isPending}>
|
|
279
|
+
{isPending ? "Publishing..." : "Publish Post"}
|
|
280
|
+
</button>
|
|
281
|
+
</form>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### `useFormMutation` Return Properties
|
|
287
|
+
|
|
288
|
+
In addition to all form handler properties, it includes:
|
|
289
|
+
- `mutate` & `mutateAsync` – Trigger mutation with typed variables.
|
|
290
|
+
- `isPending` – TanStack Query v5 pending state boolean.
|
|
291
|
+
- `isLoading` – Backwards-compatible alias for `isPending`.
|
|
292
|
+
- `isError` & `error` – Mutation error state.
|
|
293
|
+
- `formError` – Dedicated form-level error (if distinct).
|
|
294
|
+
- `data` – Response data from mutation.
|
|
295
|
+
- `reset` – Atomically resets **both** the form fields and the mutation state.
|
|
296
|
+
- `resetForm` & `resetMutation` – Independent reset triggers.
|
|
297
|
+
- `status` – Current mutation status (`'idle' | 'pending' | 'success' | 'error'`).
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Global Configuration
|
|
302
|
+
|
|
303
|
+
### 1. Factory Pattern (`createFormHandler`)
|
|
304
|
+
|
|
305
|
+
Create a pre-configured hook with company-wide defaults (such as custom notification libraries or API interceptors):
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
// lib/form.ts
|
|
309
|
+
import { createFormHandler } from "@hirely/hooks";
|
|
310
|
+
|
|
311
|
+
export const useAppForm = createFormHandler({
|
|
312
|
+
mode: "onBlur",
|
|
313
|
+
resetOptions: { resetAfterSuccess: true, keepDefaultValues: false },
|
|
314
|
+
notify: (msg, type) => {
|
|
315
|
+
if (type === "success") console.log("[Success]", msg);
|
|
316
|
+
else console.error("[Error]", msg);
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
// FeatureComponent.tsx
|
|
323
|
+
import { useAppForm } from "@/lib/form";
|
|
324
|
+
import { z } from "zod";
|
|
325
|
+
|
|
326
|
+
const schema = z.object({ query: z.string() });
|
|
327
|
+
|
|
328
|
+
export function SearchForm() {
|
|
329
|
+
// Inherits global defaults, but you can override any option locally:
|
|
330
|
+
const { register, onSubmit } = useAppForm({
|
|
331
|
+
schema,
|
|
332
|
+
endpoint: "/api/search",
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
return <form onSubmit={onSubmit}>...</form>;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### 2. React Context (`FormHandlerProvider`)
|
|
340
|
+
|
|
341
|
+
Scope configurations to sub-trees of your component hierarchy:
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
import { FormHandlerProvider } from "@hirely/hooks";
|
|
345
|
+
|
|
346
|
+
export function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
347
|
+
return (
|
|
348
|
+
<FormHandlerProvider
|
|
349
|
+
defaultOptions={{
|
|
350
|
+
axiosConfig: { headers: { "X-Admin-Scope": "true" } },
|
|
351
|
+
resetOptions: { resetAfterSuccess: false },
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
{children}
|
|
355
|
+
</FormHandlerProvider>
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Any `useFormHandler` within this tree automatically merges provider defaults with local options.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Higher-Order Component (`withFormHandler`)
|
|
365
|
+
|
|
366
|
+
For legacy class components or HOC architecture:
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
import React from "react";
|
|
370
|
+
import { withFormHandler } from "@hirely/hooks";
|
|
371
|
+
import type { useFormHandler } from "@hirely/hooks";
|
|
372
|
+
import { z } from "zod";
|
|
373
|
+
|
|
374
|
+
const feedbackSchema = z.object({
|
|
375
|
+
rating: z.number().min(1).max(5),
|
|
376
|
+
notes: z.string().optional(),
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
type FeedbackProps = {
|
|
380
|
+
formHandler: ReturnType<typeof useFormHandler<typeof feedbackSchema>>;
|
|
381
|
+
category: string;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
class FeedbackView extends React.Component<FeedbackProps> {
|
|
385
|
+
render() {
|
|
386
|
+
const { formHandler, category } = this.props;
|
|
387
|
+
return (
|
|
388
|
+
<form onSubmit={formHandler.onSubmit}>
|
|
389
|
+
<h3>Category: {category}</h3>
|
|
390
|
+
<input type="number" {...formHandler.register("rating", { valueAsNumber: true })} />
|
|
391
|
+
<button type="submit" disabled={formHandler.loading}>Submit</button>
|
|
392
|
+
</form>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export default withFormHandler(FeedbackView, {
|
|
398
|
+
schema: feedbackSchema,
|
|
399
|
+
endpoint: "/api/feedback",
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Recipes & Advanced Usage
|
|
406
|
+
|
|
407
|
+
### File Uploads with `FormData`
|
|
408
|
+
|
|
409
|
+
Enable `useFormData: true` to serialize payloads containing `File`, `Blob`, arrays of files, or text fields into standard `FormData`:
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
const uploadSchema = z.object({
|
|
413
|
+
title: z.string(),
|
|
414
|
+
avatar: z.instanceof(File),
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const { register, onSubmit, setValue } = useFormHandler({
|
|
418
|
+
schema: uploadSchema,
|
|
419
|
+
endpoint: "/api/upload",
|
|
420
|
+
useFormData: true,
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Request Cancellation
|
|
425
|
+
|
|
426
|
+
Set `enableAbort: true` to get an `abort()` handle that cleanly cancels running requests:
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
const { onSubmit, abort, loading } = useFormHandler({
|
|
430
|
+
schema: largeReportSchema,
|
|
431
|
+
endpoint: "/api/generate-report",
|
|
432
|
+
enableAbort: true,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<form onSubmit={onSubmit}>
|
|
437
|
+
<button type="submit" disabled={loading}>Generate</button>
|
|
438
|
+
{loading && <button type="button" onClick={abort}>Cancel</button>}
|
|
439
|
+
</form>
|
|
440
|
+
);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Optimistic Updates
|
|
444
|
+
|
|
445
|
+
Use `onMutate` to perform client-side updates before the request completes and rollback on error:
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
const { onSubmit } = useFormHandler({
|
|
449
|
+
schema: updateTaskSchema,
|
|
450
|
+
endpoint: "/api/tasks/1",
|
|
451
|
+
onMutate: async (newData) => {
|
|
452
|
+
const previous = currentTask;
|
|
453
|
+
setTask((prev) => ({ ...prev, ...newData }));
|
|
454
|
+
return { previous }; // passed as `context`
|
|
455
|
+
},
|
|
456
|
+
onError: (error, context) => {
|
|
457
|
+
// Rollback to original state
|
|
458
|
+
setTask(context.previous);
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Using Native `fetch`
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
const { onSubmit } = useFormHandler({
|
|
467
|
+
schema: itemSchema,
|
|
468
|
+
service: async (data) => {
|
|
469
|
+
const res = await fetch("/api/items", {
|
|
470
|
+
method: "POST",
|
|
471
|
+
headers: { "Content-Type": "application/json" },
|
|
472
|
+
body: JSON.stringify(data),
|
|
473
|
+
});
|
|
474
|
+
if (!res.ok) throw new Error("Request failed with status " + res.status);
|
|
475
|
+
return res.json();
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Custom Response & Error Parsers
|
|
481
|
+
|
|
482
|
+
Adapt `@hirely/hooks` to any API response schema:
|
|
483
|
+
|
|
484
|
+
```tsx
|
|
485
|
+
useFormHandler({
|
|
486
|
+
schema: mySchema,
|
|
487
|
+
endpoint: "/api/custom",
|
|
488
|
+
parseResponse: (res) => ({
|
|
489
|
+
success: res.code === 200,
|
|
490
|
+
message: res.statusText,
|
|
491
|
+
data: res.payload,
|
|
492
|
+
}),
|
|
493
|
+
parseError: (err) => ({
|
|
494
|
+
message: err.response?.data?.errorDescription || err.message || "Operation failed",
|
|
495
|
+
}),
|
|
496
|
+
});
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## License
|
|
502
|
+
|
|
503
|
+
[MIT](LICENSE) © Hirely
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { UseFormHandlerOptions } from "./useFormHandler";
|
|
3
|
+
import type { ZodType } from "zod";
|
|
4
|
+
import type { FieldValues } from "react-hook-form";
|
|
5
|
+
export declare const FormHandlerProvider: <TSchema extends ZodType<FieldValues, any, any> = ZodType<FieldValues, any, any>>({ children, defaultOptions, }: {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
defaultOptions: Partial<UseFormHandlerOptions<TSchema>>;
|
|
8
|
+
}) => import("react").JSX.Element;
|
|
9
|
+
export declare const useFormHandlerContext: () => Partial<UseFormHandlerOptions<any, any>>;
|
|
10
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,eAAO,MAAM,mBAAmB,GAAI,OAAO,SAAS,OAAO,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,iCAGhH;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC;CACzD,gCAOA,CAAC;AAEF,eAAO,MAAM,qBAAqB,gDAAuC,CAAC"}
|