form-hook-kit 1.2.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/LICENSE +21 -0
- package/README.md +753 -0
- package/dist/FormContext.d.ts +8 -0
- package/dist/FormContext.d.ts.map +1 -0
- package/dist/FormProvider.d.ts +33 -0
- package/dist/FormProvider.d.ts.map +1 -0
- package/dist/devtools/FormDevTools.d.ts +86 -0
- package/dist/devtools/FormDevTools.d.ts.map +1 -0
- package/dist/devtools/index.d.ts +3 -0
- package/dist/devtools/index.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useForm.d.ts +30 -0
- package/dist/hooks/useForm.d.ts.map +1 -0
- package/dist/hooks/useFormField.d.ts +40 -0
- package/dist/hooks/useFormField.d.ts.map +1 -0
- package/dist/hooks/useFormPerformance.d.ts +57 -0
- package/dist/hooks/useFormPerformance.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +394 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +408 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +94 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/debounce.d.ts +6 -0
- package/dist/utils/debounce.d.ts.map +1 -0
- package/dist/utils/errors.d.ts +11 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/formActions.d.ts +57 -0
- package/dist/utils/formActions.d.ts.map +1 -0
- package/dist/utils/formReducer.d.ts +7 -0
- package/dist/utils/formReducer.d.ts.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/performance.d.ts +7 -0
- package/dist/utils/performance.d.ts.map +1 -0
- package/dist/utils/validation.d.ts +25 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/package.json +91 -0
package/README.md
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
# form-hook-kit
|
|
2
|
+
|
|
3
|
+
A lightweight, flexible form management library for React and React Native with a hooks-based API and Yup validation.
|
|
4
|
+
|
|
5
|
+
**Author:** bc-bane
|
|
6
|
+
**License:** MIT
|
|
7
|
+
**Platform tested:** React 18+ & React Native
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Type-safe** - Full TypeScript support with 100% type coverage
|
|
14
|
+
- **Cross-platform** - Works with React and React Native
|
|
15
|
+
- **React Native Compatible** - No native iOS/Android code - works with legacy architecture, new architecture, and Android 16KB page size
|
|
16
|
+
- **Validation** - Powered by Yup for robust schema validation
|
|
17
|
+
- **Hooks-based** - Modern React hooks API
|
|
18
|
+
- **Context-based** - Efficient state management with React Context
|
|
19
|
+
- **Performant** - Memoized functions, optional debounced validation, minimal re-renders
|
|
20
|
+
- **Developer Tools** - Built-in DevTools component for debugging (development only)
|
|
21
|
+
- **Flexible** - Works with any UI component library
|
|
22
|
+
- **Well-tested** - 100% test coverage
|
|
23
|
+
- **Production-ready** - Comprehensive documentation and examples
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Why form-hook-kit?
|
|
28
|
+
|
|
29
|
+
### Comparison with Other Form Libraries
|
|
30
|
+
|
|
31
|
+
#### vs. Formik
|
|
32
|
+
|
|
33
|
+
**Key Differences:**
|
|
34
|
+
- **API Style** - form-hook-kit is hooks-only, Formik supports both hooks and render props
|
|
35
|
+
- **React Native** - Both support React Native with different approaches
|
|
36
|
+
- **Yup Integration** - Both have built-in Yup support
|
|
37
|
+
- **Community** - Formik has larger community and more plugins
|
|
38
|
+
|
|
39
|
+
**When to use Formik instead:**
|
|
40
|
+
- You need a battle-tested library with years of production use
|
|
41
|
+
- You want access to a larger ecosystem of community plugins
|
|
42
|
+
- Your project already uses Formik
|
|
43
|
+
|
|
44
|
+
#### vs. React Hook Form
|
|
45
|
+
|
|
46
|
+
**Key Differences:**
|
|
47
|
+
- **State Management** - form-hook-kit uses Context, React Hook Form uses refs
|
|
48
|
+
- **Input Style** - form-hook-kit uses controlled inputs, React Hook Form uses uncontrolled
|
|
49
|
+
- **Yup Integration** - form-hook-kit has built-in support, React Hook Form requires resolver
|
|
50
|
+
- **Performance** - React Hook Form may be faster for very large forms
|
|
51
|
+
- **Popularity** - React Hook Form has larger user base
|
|
52
|
+
|
|
53
|
+
**When to use React Hook Form instead:**
|
|
54
|
+
- You need uncontrolled inputs for maximum performance
|
|
55
|
+
- You prefer ref-based form tracking
|
|
56
|
+
- You have very large forms (100+ fields)
|
|
57
|
+
|
|
58
|
+
#### vs. Final Form
|
|
59
|
+
|
|
60
|
+
**Key Differences:**
|
|
61
|
+
- **API Style** - form-hook-kit is hooks-only, Final Form uses subscriptions
|
|
62
|
+
- **TypeScript** - form-hook-kit is TypeScript-first, Final Form has TypeScript support
|
|
63
|
+
- **React Version** - form-hook-kit requires React 16.8+, Final Form supports older versions
|
|
64
|
+
- **Maturity** - Final Form is more established
|
|
65
|
+
|
|
66
|
+
**When to use Final Form instead:**
|
|
67
|
+
- You need field-level subscriptions for complex performance optimization
|
|
68
|
+
- You're migrating from Redux Form
|
|
69
|
+
- You need to support older React versions
|
|
70
|
+
|
|
71
|
+
#### vs. Custom Solutions
|
|
72
|
+
|
|
73
|
+
**Advantages over rolling your own:**
|
|
74
|
+
- **Well-tested** - 100% test coverage with comprehensive test suite
|
|
75
|
+
- **Edge cases handled** - Validation, error handling, field refs all included
|
|
76
|
+
- **TypeScript support** - Full type safety out of the box
|
|
77
|
+
- **Ready to use** - No need to build and maintain form infrastructure
|
|
78
|
+
- **Documentation** - Complete docs and examples
|
|
79
|
+
|
|
80
|
+
### Feature Comparison Table
|
|
81
|
+
|
|
82
|
+
| Feature | form-hook-kit | Formik | React Hook Form | Final Form |
|
|
83
|
+
|---------|-------------------|--------|-----------------|------------|
|
|
84
|
+
| React Native Support | ✓ Native | ✓ Supported | ✓ Supported | ✓ Supported |
|
|
85
|
+
| TypeScript | ✓ First-class | ✓ Supported | ✓ First-class | ✓ Supported |
|
|
86
|
+
| Yup Integration | ✓ Built-in | ✓ Built-in | Via resolver | Via plugin |
|
|
87
|
+
| API Style | Context + Hooks | Context + Hooks | Refs + Hooks | Subscriptions |
|
|
88
|
+
| Hooks-only API | ✓ Yes | Mixed | ✓ Yes | Mixed |
|
|
89
|
+
| Built-in Field Refs | ✓ Yes | ✓ Yes | ✓ Yes | ✓ Yes |
|
|
90
|
+
| Learning Curve | Low | Low-Medium | Medium | Medium-High |
|
|
91
|
+
|
|
92
|
+
### When to Choose form-hook-kit
|
|
93
|
+
|
|
94
|
+
Choose `form-hook-kit` if you:
|
|
95
|
+
- Are building a React or React Native app
|
|
96
|
+
- Need React Native new architecture compatibility (no native code dependencies)
|
|
97
|
+
- Want a simple, hooks-based API
|
|
98
|
+
- Prefer context-based state management
|
|
99
|
+
- Need TypeScript support with 100% type coverage
|
|
100
|
+
- Use Yup for validation
|
|
101
|
+
- Want built-in Yup integration without extra packages
|
|
102
|
+
- Prefer controlled inputs over uncontrolled
|
|
103
|
+
- Need compatibility with Android 16KB page size
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Installation
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm install form-hook-kit yup
|
|
111
|
+
# or
|
|
112
|
+
yarn add form-hook-kit yup
|
|
113
|
+
# or
|
|
114
|
+
pnpm add form-hook-kit yup
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Quick Start
|
|
120
|
+
|
|
121
|
+
### Basic Example (React Web)
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import React from 'react';
|
|
125
|
+
import * as yup from 'yup';
|
|
126
|
+
import {FormProvider, useForm, useFormField} from 'form-hook-kit';
|
|
127
|
+
|
|
128
|
+
// Define your validation schema
|
|
129
|
+
const schema = yup.object({
|
|
130
|
+
email: yup.string().email('Invalid email').required('Email is required'),
|
|
131
|
+
password: yup
|
|
132
|
+
.string()
|
|
133
|
+
.min(8, 'Password must be at least 8 characters')
|
|
134
|
+
.required('Password is required'),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Create form fields
|
|
138
|
+
function EmailField() {
|
|
139
|
+
const {value, error, onChange, onBlur} = useFormField('email');
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div>
|
|
143
|
+
<input
|
|
144
|
+
type="email"
|
|
145
|
+
value={value || ''}
|
|
146
|
+
onChange={onChange}
|
|
147
|
+
onBlur={onBlur}
|
|
148
|
+
placeholder="Email"
|
|
149
|
+
/>
|
|
150
|
+
{error && <span className="error">{error}</span>}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function PasswordField() {
|
|
156
|
+
const {value, error, onChange, onBlur} = useFormField('password');
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div>
|
|
160
|
+
<input
|
|
161
|
+
type="password"
|
|
162
|
+
value={value || ''}
|
|
163
|
+
onChange={onChange}
|
|
164
|
+
onBlur={onBlur}
|
|
165
|
+
placeholder="Password"
|
|
166
|
+
/>
|
|
167
|
+
{error && <span className="error">{error}</span>}
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Create your form
|
|
173
|
+
function LoginForm() {
|
|
174
|
+
const {validateForm, values} = useForm();
|
|
175
|
+
|
|
176
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
const errors = validateForm();
|
|
179
|
+
|
|
180
|
+
if (Object.values(errors).every(error => !error)) {
|
|
181
|
+
// Form is valid, submit it
|
|
182
|
+
console.log('Form submitted:', values);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<form onSubmit={handleSubmit}>
|
|
188
|
+
<EmailField />
|
|
189
|
+
<PasswordField />
|
|
190
|
+
<button type="submit">Login</button>
|
|
191
|
+
</form>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Wrap with FormProvider
|
|
196
|
+
export default function App() {
|
|
197
|
+
return (
|
|
198
|
+
<FormProvider
|
|
199
|
+
schema={schema}
|
|
200
|
+
initialValues={{email: '', password: ''}}
|
|
201
|
+
>
|
|
202
|
+
<LoginForm />
|
|
203
|
+
</FormProvider>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### React Native Example
|
|
209
|
+
|
|
210
|
+
> **Note:** form-hook-kit has **zero native dependencies** - it's pure JavaScript/TypeScript. This means it's fully compatible with:
|
|
211
|
+
> - ✅ React Native legacy architecture
|
|
212
|
+
> - ✅ React Native new architecture (Fabric & TurboModules)
|
|
213
|
+
> - ✅ Android 16KB page size requirement
|
|
214
|
+
> - ✅ Expo (including Expo Go)
|
|
215
|
+
>
|
|
216
|
+
> **📱 For detailed React Native usage and platform-specific features, see [REACT_NATIVE.md](REACT_NATIVE.md)**
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import React from 'react';
|
|
220
|
+
import {View, TextInput, Text, Button} from 'react-native';
|
|
221
|
+
import * as yup from 'yup';
|
|
222
|
+
import {FormProvider, useFormField, useForm} from 'form-hook-kit';
|
|
223
|
+
|
|
224
|
+
const schema = yup.object({
|
|
225
|
+
username: yup.string().required('Username is required'),
|
|
226
|
+
email: yup.string().email('Invalid email').required('Email is required'),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
function UsernameField() {
|
|
230
|
+
const {value, error, onChangeValue, onBlur, setRef} = useFormField('username');
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<View>
|
|
234
|
+
<TextInput
|
|
235
|
+
ref={setRef}
|
|
236
|
+
value={value || ''}
|
|
237
|
+
onChangeText={onChangeValue}
|
|
238
|
+
onBlur={onBlur}
|
|
239
|
+
placeholder="Username"
|
|
240
|
+
/>
|
|
241
|
+
{error && <Text style={{color: 'red'}}>{error}</Text>}
|
|
242
|
+
</View>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function EmailField() {
|
|
247
|
+
const {value, error, onChangeValue, onBlur, setRef} = useFormField('email');
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<View>
|
|
251
|
+
<TextInput
|
|
252
|
+
ref={setRef}
|
|
253
|
+
value={value || ''}
|
|
254
|
+
onChangeText={onChangeValue}
|
|
255
|
+
onBlur={onBlur}
|
|
256
|
+
placeholder="Email"
|
|
257
|
+
keyboardType="email-address"
|
|
258
|
+
/>
|
|
259
|
+
{error && <Text style={{color: 'red'}}>{error}</Text>}
|
|
260
|
+
</View>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function SignupForm() {
|
|
265
|
+
const {validateForm, values} = useForm();
|
|
266
|
+
|
|
267
|
+
const handleSubmit = () => {
|
|
268
|
+
const errors = validateForm();
|
|
269
|
+
|
|
270
|
+
if (Object.values(errors).every(error => !error)) {
|
|
271
|
+
console.log('Form submitted:', values);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<View>
|
|
277
|
+
<UsernameField />
|
|
278
|
+
<EmailField />
|
|
279
|
+
<Button title="Sign Up" onPress={handleSubmit} />
|
|
280
|
+
</View>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export default function App() {
|
|
285
|
+
return (
|
|
286
|
+
<FormProvider
|
|
287
|
+
schema={schema}
|
|
288
|
+
initialValues={{username: '', email: ''}}
|
|
289
|
+
>
|
|
290
|
+
<SignupForm />
|
|
291
|
+
</FormProvider>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## API Reference
|
|
299
|
+
|
|
300
|
+
For advanced usage including custom form implementations and direct access to utility functions, see [ADVANCED_USAGE.md](./ADVANCED_USAGE.md).
|
|
301
|
+
|
|
302
|
+
### `FormProvider`
|
|
303
|
+
|
|
304
|
+
The main component that wraps your form and provides context.
|
|
305
|
+
|
|
306
|
+
**Props:**
|
|
307
|
+
|
|
308
|
+
- `schema` (required): Yup validation schema
|
|
309
|
+
- `initialValues` (optional): Initial form values (default: `{}`)
|
|
310
|
+
- `initialErrors` (optional): Initial error state (default: `{}`)
|
|
311
|
+
- `validationDebounce` (optional): Debounce validation in milliseconds for performance optimization (default: `0`)
|
|
312
|
+
- `children` (required): Form components
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
<FormProvider
|
|
316
|
+
schema={yupSchema}
|
|
317
|
+
initialValues={{email: '', password: ''}}
|
|
318
|
+
initialErrors={{}}
|
|
319
|
+
validationDebounce={300} // Optional: debounce validation for better performance
|
|
320
|
+
>
|
|
321
|
+
{children}
|
|
322
|
+
</FormProvider>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### `useForm()`
|
|
326
|
+
|
|
327
|
+
Hook to access the form context. Must be used within a `FormProvider`.
|
|
328
|
+
|
|
329
|
+
**Returns:**
|
|
330
|
+
|
|
331
|
+
- `values`: Object containing all form values
|
|
332
|
+
- `errors`: Object containing all form errors
|
|
333
|
+
- `fieldRefs`: Ref object for managing field focus
|
|
334
|
+
- `changeValue(params)`: Function to update a single field
|
|
335
|
+
- `changeValues(values)`: Function to update multiple fields
|
|
336
|
+
- `clearError(name)`: Function to clear error for a field
|
|
337
|
+
- `setError(params)`: Function to set error for a field
|
|
338
|
+
- `validateField(name, value?)`: Function to validate a single field
|
|
339
|
+
- `validateForm()`: Function to validate entire form
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
const {
|
|
343
|
+
values,
|
|
344
|
+
errors,
|
|
345
|
+
changeValue,
|
|
346
|
+
validateForm,
|
|
347
|
+
fieldRefs,
|
|
348
|
+
} = useForm();
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### `useFormField(name)`
|
|
352
|
+
|
|
353
|
+
Convenient hook for managing a single form field. Provides pre-configured handlers.
|
|
354
|
+
|
|
355
|
+
**Parameters:**
|
|
356
|
+
|
|
357
|
+
- `name` (string): The field name
|
|
358
|
+
|
|
359
|
+
**Returns:**
|
|
360
|
+
|
|
361
|
+
- `value`: Current field value
|
|
362
|
+
- `error`: Current field error
|
|
363
|
+
- `name`: Field name
|
|
364
|
+
- `onChange`: Handler for React web inputs (e.target.value)
|
|
365
|
+
- `onChangeValue`: Handler for React Native or custom value changes
|
|
366
|
+
- `onBlur`: Handler for field blur (triggers validation)
|
|
367
|
+
- `onFocus`: Handler for field focus (clears errors)
|
|
368
|
+
- `setRef`: Function to set field ref for focus management
|
|
369
|
+
|
|
370
|
+
```tsx
|
|
371
|
+
const {value, error, onChange, onBlur} = useFormField('email');
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### `useFormPerformance()`
|
|
375
|
+
|
|
376
|
+
**NEW in v1.2.0** - Hook to monitor form performance metrics. Useful for debugging and optimization.
|
|
377
|
+
|
|
378
|
+
**Returns:**
|
|
379
|
+
|
|
380
|
+
- `renderCount`: Number of times the component has rendered
|
|
381
|
+
- `lastRenderTime`: Time taken for the last render (ms)
|
|
382
|
+
- `validationCount`: Number of validations performed
|
|
383
|
+
- `averageValidationTime`: Average validation time (ms)
|
|
384
|
+
- `fieldChangeCount`: Number of field changes
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import {useFormPerformance} from 'form-hook-kit';
|
|
388
|
+
|
|
389
|
+
function MyForm() {
|
|
390
|
+
const metrics = useFormPerformance();
|
|
391
|
+
|
|
392
|
+
console.log('Renders:', metrics.renderCount);
|
|
393
|
+
console.log('Avg validation time:', metrics.averageValidationTime);
|
|
394
|
+
|
|
395
|
+
return <div>...</div>;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### `useValidationPerformance()`
|
|
400
|
+
|
|
401
|
+
**NEW in v1.2.0** - Hook to track validation performance with timing metrics.
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
import {useValidationPerformance} from 'form-hook-kit';
|
|
405
|
+
|
|
406
|
+
function MyForm() {
|
|
407
|
+
const {validateField, validateForm, metrics} = useValidationPerformance();
|
|
408
|
+
|
|
409
|
+
const handleSubmit = () => {
|
|
410
|
+
validateForm();
|
|
411
|
+
console.log('Validation took:', metrics.lastValidationTime, 'ms');
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
return <button onClick={handleSubmit}>Submit</button>;
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### `FormDevTools`
|
|
419
|
+
|
|
420
|
+
**NEW in v1.2.0** - Cross-platform development tool for inspecting form state and performance.
|
|
421
|
+
|
|
422
|
+
**Platform Support:**
|
|
423
|
+
- **Web**: Visual UI panel (default)
|
|
424
|
+
- **React Native**: Console logging mode (works with React Native Debugger, Flipper, or Metro logs)
|
|
425
|
+
|
|
426
|
+
**Import from separate entry point:**
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import {FormDevTools} from 'form-hook-kit/devtools';
|
|
430
|
+
|
|
431
|
+
// Web - shows UI panel
|
|
432
|
+
function WebForm() {
|
|
433
|
+
return (
|
|
434
|
+
<FormProvider schema={schema}>
|
|
435
|
+
<MyFormFields />
|
|
436
|
+
{process.env.NODE_ENV === 'development' && <FormDevTools />}
|
|
437
|
+
</FormProvider>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// React Native - logs to console
|
|
442
|
+
function MobileForm() {
|
|
443
|
+
return (
|
|
444
|
+
<FormProvider schema={schema}>
|
|
445
|
+
<MyFormFields />
|
|
446
|
+
{__DEV__ && <FormDevTools mode="console" autoLog />}
|
|
447
|
+
</FormProvider>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Props:**
|
|
453
|
+
- `mode?: 'ui' | 'console' | 'none'` - Display mode (auto-detects platform by default)
|
|
454
|
+
- `autoLog?: boolean` - Auto-log changes in console mode (default: `false`)
|
|
455
|
+
- `logPrefix?: string` - Custom log prefix (default: `'[FormDevTools]'`)
|
|
456
|
+
- `position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'` - UI panel position (default: `'bottom-right'`)
|
|
457
|
+
- `defaultOpen?: boolean` - UI panel open by default (default: `true`)
|
|
458
|
+
|
|
459
|
+
**Console Mode (React Native):**
|
|
460
|
+
|
|
461
|
+
When using `mode="console"`, you can manually inspect form state:
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
// In React Native Debugger or browser console:
|
|
465
|
+
global.formDevTools.logValues() // Log current values
|
|
466
|
+
global.formDevTools.logErrors() // Log current errors
|
|
467
|
+
global.formDevTools.logMetrics() // Log performance metrics
|
|
468
|
+
global.formDevTools.logAll() // Log everything
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Props:**
|
|
472
|
+
|
|
473
|
+
- `position` (optional): `'top-left'` | `'top-right'` | `'bottom-left'` | `'bottom-right'` (default: `'bottom-right'`)
|
|
474
|
+
- `defaultOpen` (optional): Whether to show by default (default: `false`)
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Advanced Usage
|
|
479
|
+
|
|
480
|
+
### Performance Optimization
|
|
481
|
+
|
|
482
|
+
**Debounced Validation:**
|
|
483
|
+
|
|
484
|
+
For forms with real-time validation, you can debounce validation to improve performance:
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
<FormProvider
|
|
488
|
+
schema={schema}
|
|
489
|
+
validationDebounce={300} // Wait 300ms after user stops typing
|
|
490
|
+
>
|
|
491
|
+
<MyForm />
|
|
492
|
+
</FormProvider>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Performance Monitoring:**
|
|
496
|
+
|
|
497
|
+
Track and optimize your form's performance:
|
|
498
|
+
|
|
499
|
+
```tsx
|
|
500
|
+
import {useFormPerformance} from 'form-hook-kit';
|
|
501
|
+
|
|
502
|
+
function MyForm() {
|
|
503
|
+
const metrics = useFormPerformance();
|
|
504
|
+
|
|
505
|
+
// Log performance in development
|
|
506
|
+
useEffect(() => {
|
|
507
|
+
if (process.env.NODE_ENV === 'development') {
|
|
508
|
+
console.log('Form Performance:', metrics);
|
|
509
|
+
}
|
|
510
|
+
}, [metrics]);
|
|
511
|
+
|
|
512
|
+
return <div>...</div>;
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Custom Validation
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
import * as yup from 'yup';
|
|
520
|
+
|
|
521
|
+
const schema = yup.object({
|
|
522
|
+
password: yup.string().required('Password is required'),
|
|
523
|
+
confirmPassword: yup
|
|
524
|
+
.string()
|
|
525
|
+
.oneOf([yup.ref('password')], 'Passwords must match')
|
|
526
|
+
.required('Confirm password is required'),
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Manual Field Validation
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
function MyField() {
|
|
534
|
+
const {values, errors, changeValue, validateField} = useForm();
|
|
535
|
+
|
|
536
|
+
const handleChange = (value: string) => {
|
|
537
|
+
changeValue({name: 'email', value});
|
|
538
|
+
// Validate immediately on change
|
|
539
|
+
validateField('email', value);
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
<input
|
|
544
|
+
value={values.email || ''}
|
|
545
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
546
|
+
/>
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Setting Errors Manually
|
|
552
|
+
|
|
553
|
+
```tsx
|
|
554
|
+
function MyForm() {
|
|
555
|
+
const {setError, clearError} = useForm();
|
|
556
|
+
|
|
557
|
+
const handleCustomValidation = () => {
|
|
558
|
+
if (someCondition) {
|
|
559
|
+
setError({name: 'email', error: 'Custom error message'});
|
|
560
|
+
} else {
|
|
561
|
+
clearError('email');
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
return (
|
|
566
|
+
// ...
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Focus Management (React Native)
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
function MyForm() {
|
|
575
|
+
const {fieldRefs} = useForm();
|
|
576
|
+
|
|
577
|
+
const focusNextField = () => {
|
|
578
|
+
// Focus the email field
|
|
579
|
+
fieldRefs.current.email?.focus();
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
return (
|
|
583
|
+
<View>
|
|
584
|
+
<TextInput
|
|
585
|
+
ref={(ref) => (fieldRefs.current.username = ref)}
|
|
586
|
+
onSubmitEditing={() => fieldRefs.current.email?.focus()}
|
|
587
|
+
returnKeyType="next"
|
|
588
|
+
/>
|
|
589
|
+
<TextInput
|
|
590
|
+
ref={(ref) => (fieldRefs.current.email = ref)}
|
|
591
|
+
returnKeyType="done"
|
|
592
|
+
/>
|
|
593
|
+
</View>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Updating Multiple Values
|
|
599
|
+
|
|
600
|
+
```tsx
|
|
601
|
+
function MyForm() {
|
|
602
|
+
const {changeValues} = useForm();
|
|
603
|
+
|
|
604
|
+
const resetForm = () => {
|
|
605
|
+
changeValues({
|
|
606
|
+
email: '',
|
|
607
|
+
password: '',
|
|
608
|
+
username: '',
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
return (
|
|
613
|
+
<button onClick={resetForm}>Reset</button>
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Custom Formatters
|
|
619
|
+
|
|
620
|
+
```tsx
|
|
621
|
+
function PhoneField() {
|
|
622
|
+
const {value, error, onChangeValue, onBlur} = useFormField('phone');
|
|
623
|
+
|
|
624
|
+
const formatPhone = (text: string) => {
|
|
625
|
+
// Remove non-digits
|
|
626
|
+
const digits = text.replace(/\D/g, '');
|
|
627
|
+
// Format as (XXX) XXX-XXXX
|
|
628
|
+
if (digits.length <= 3) return digits;
|
|
629
|
+
if (digits.length <= 6) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
|
|
630
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6, 10)}`;
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
return (
|
|
634
|
+
<input
|
|
635
|
+
value={value || ''}
|
|
636
|
+
onChange={(e) => onChangeValue(formatPhone(e.target.value))}
|
|
637
|
+
onBlur={onBlur}
|
|
638
|
+
/>
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## TypeScript Support
|
|
646
|
+
|
|
647
|
+
Full TypeScript support with type inference:
|
|
648
|
+
|
|
649
|
+
```tsx
|
|
650
|
+
import {FormProvider, useForm, useFormField} from 'form-hook-kit';
|
|
651
|
+
import * as yup from 'yup';
|
|
652
|
+
|
|
653
|
+
interface FormValues {
|
|
654
|
+
email: string;
|
|
655
|
+
password: string;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const schema = yup.object({
|
|
659
|
+
email: yup.string().email().required(),
|
|
660
|
+
password: yup.string().min(8).required(),
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
function MyForm() {
|
|
664
|
+
const {values, errors} = useForm();
|
|
665
|
+
// values and errors are properly typed based on schema
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
## Testing
|
|
672
|
+
|
|
673
|
+
Example test using React Testing Library:
|
|
674
|
+
|
|
675
|
+
```tsx
|
|
676
|
+
import {render, screen, fireEvent} from '@testing-library/react';
|
|
677
|
+
import * as yup from 'yup';
|
|
678
|
+
import {FormProvider, useForm} from 'form-hook-kit';
|
|
679
|
+
|
|
680
|
+
const schema = yup.object({
|
|
681
|
+
email: yup.string().email().required(),
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test('validates email field', async () => {
|
|
685
|
+
function TestForm() {
|
|
686
|
+
const {values, errors, changeValue, validateField} = useForm();
|
|
687
|
+
|
|
688
|
+
return (
|
|
689
|
+
<div>
|
|
690
|
+
<input
|
|
691
|
+
data-testid="email"
|
|
692
|
+
value={values.email || ''}
|
|
693
|
+
onChange={(e) => changeValue({name: 'email', value: e.target.value})}
|
|
694
|
+
onBlur={() => validateField('email')}
|
|
695
|
+
/>
|
|
696
|
+
{errors.email && <span data-testid="error">{errors.email}</span>}
|
|
697
|
+
</div>
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
render(
|
|
702
|
+
<FormProvider schema={schema} initialValues={{email: ''}}>
|
|
703
|
+
<TestForm />
|
|
704
|
+
</FormProvider>
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
const input = screen.getByTestId('email');
|
|
708
|
+
fireEvent.change(input, {target: {value: 'invalid'}});
|
|
709
|
+
fireEvent.blur(input);
|
|
710
|
+
|
|
711
|
+
expect(await screen.findByTestId('error')).toBeInTheDocument();
|
|
712
|
+
});
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
## Performance Tips
|
|
718
|
+
|
|
719
|
+
1. **Memoize handlers**: Use `useCallback` for event handlers if needed
|
|
720
|
+
2. **Split forms**: Break large forms into smaller sub-forms
|
|
721
|
+
3. **Lazy validation**: Validate on blur instead of on every keystroke
|
|
722
|
+
4. **Schema optimization**: Keep Yup schemas simple and efficient
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## Troubleshooting
|
|
727
|
+
|
|
728
|
+
| Issue | Solution |
|
|
729
|
+
|-------|----------|
|
|
730
|
+
| Validation not working | Ensure your Yup schema is properly defined and passed to FormProvider |
|
|
731
|
+
| Field not updating | Check that you're using the correct field name and onChange handler |
|
|
732
|
+
| TypeScript errors | Make sure your form values interface matches your Yup schema |
|
|
733
|
+
| React Native TextInput issues | Use `onChangeText` with `onChangeValue` instead of `onChange` |
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## Contributing
|
|
738
|
+
|
|
739
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
## License
|
|
744
|
+
|
|
745
|
+
MIT License - see [LICENSE](LICENSE) file for details
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Credits
|
|
750
|
+
|
|
751
|
+
**Author:** bc-bane
|
|
752
|
+
Built for production use in React and React Native applications.
|
|
753
|
+
Designed to be simple, performant, and flexible.
|