@n8x/react-form-utils 1.0.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 ADDED
@@ -0,0 +1,1238 @@
1
+ # n8x-form-utils
2
+
3
+ A powerful React form library that simplifies form creation with instant JavaScript object-to-form generation, Zod validation, controlled form submission events, and state management using React Hook Form. <b>A fastest way to create and manage forms in react</b>.
4
+
5
+ **Features:**
6
+ - Rapid form generation from simple field configuration
7
+ - Built-in Zod validation with type safety
8
+ - Multiple pre-built styling themes
9
+ - Grouped form support for complex layouts
10
+ - Controlled form submission handling
11
+ - Query hooks for API integration
12
+ - Support for 10+ field types
13
+
14
+ ---
15
+
16
+ ## Table of Contents
17
+
18
+ - [Installation](#installation)
19
+ - [Setup](#setup)
20
+ - [Quick Start](#quick-start)
21
+ - [Field Types](#field-types)
22
+ - [Validation](#validation)
23
+ - [Styling Themes](#styling-themes)
24
+ - [Grouped Forms](#grouped-forms)
25
+ - [Form Submission](#form-submission)
26
+ - [Data Fetching Hooks](#data-fetching-hooks)
27
+ - [Error Handling](#error-handling)
28
+ - [API Reference](#api-reference)
29
+ - [Contribution](#Contributing)
30
+
31
+ ---
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ npm install n8x-form-utils
37
+ ```
38
+
39
+ **Dependencies (auto-installed):**
40
+ - React 18+
41
+ - React Hook Form 7+
42
+ - Zod 4+
43
+ - Tailwind CSS 4+
44
+ - Axios
45
+
46
+ ---
47
+
48
+ ## Setup
49
+
50
+ ### 1. Wrap Your App with FormContextProvider
51
+
52
+ ```tsx
53
+ import { ReactNode } from 'react'
54
+ import { FormContext } from 'n8x-form-utils'
55
+ import FormContextProvider from 'n8x-form-utils'
56
+
57
+ export default function App() {
58
+ return (
59
+ <FormContextProvider>
60
+ {/* Your routes and components */}
61
+ </FormContextProvider>
62
+ )
63
+ }
64
+ ```
65
+
66
+ The `FormContextProvider` manages the global form state and validation schema. It should wrap your entire application or at least the components using N8xForm.
67
+
68
+ ---
69
+
70
+ ## Quick Start
71
+
72
+ ### Create a Simple Login Form
73
+
74
+ **Step 1: Define your form fields**
75
+
76
+ ```tsx
77
+ // forms/login.ts
78
+ import { z } from 'n8x-form-utils'
79
+ import { FormFieldTypes, FormFieldWidth, type FormFields } from 'n8x-form-utils'
80
+
81
+ export const loginForm: FormFields[] = [
82
+ {
83
+ name: 'email',
84
+ type: FormFieldTypes.EMAIL,
85
+ label: 'Email',
86
+ placeholder: 'hello@example.com',
87
+ required: true,
88
+ width: FormFieldWidth.FULL,
89
+ validationScheme: z.string().email('Please enter a valid email')
90
+ },
91
+ {
92
+ name: 'password',
93
+ type: FormFieldTypes.PASSWORD,
94
+ label: 'Password',
95
+ placeholder: 'Enter your password',
96
+ required: true,
97
+ width: FormFieldWidth.FULL,
98
+ validationScheme: z.string().min(6, 'Password must be at least 6 characters')
99
+ }
100
+ ]
101
+ ```
102
+
103
+ **Step 2: Use the form in your component**
104
+
105
+ ```tsx
106
+ // pages/auth/Login.tsx
107
+ import { useContext } from 'react'
108
+ import { N8xForm, useN8xFormQuery, EDefaultFieldStyles } from 'n8x-form-utils'
109
+ import { loginForm } from '../../forms/login'
110
+ import { login } from '../../service/auth-service'
111
+
112
+ export default function LoginPage() {
113
+ const { data, execute, error, isLoading } = useN8xFormQuery(login)
114
+
115
+ const handleLoginSubmit = async (formData: any) => {
116
+ execute(formData)
117
+ }
118
+
119
+ return (
120
+ <div className="w-full min-h-screen flex justify-center items-center">
121
+ <div className="md:w-[600px] w-full sm:p-6 p-3">
122
+ <h1 className="text-4xl tracking-tighter">Account Login</h1>
123
+
124
+ <N8xForm
125
+ fields={loginForm}
126
+ onShubmit={handleLoginSubmit}
127
+ defaultFieldStyles={EDefaultFieldStyles.FLOATING_LABEL}
128
+ >
129
+ <button
130
+ disabled={isLoading}
131
+ type="submit"
132
+ className="col-span-4 bg-blue-500 disabled:bg-zinc-400 text-white py-2 px-4 rounded font-medium"
133
+ >
134
+ {isLoading ? 'Logging in...' : 'Login'}
135
+ </button>
136
+ </N8xForm>
137
+
138
+ {error && <p className="text-red-500 text-center mt-4">{error}</p>}
139
+ </div>
140
+ </div>
141
+ )
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Field Types
148
+
149
+ N8x Form Utils supports 10+ field types. Here's a comprehensive example showing all available field types:
150
+
151
+ ### Basic Fields
152
+
153
+ #### TEXT Field
154
+ ```tsx
155
+ {
156
+ name: 'username',
157
+ type: FormFieldTypes.TEXT,
158
+ label: 'Username',
159
+ placeholder: 'Enter your username',
160
+ required: true,
161
+ width: FormFieldWidth.FULL,
162
+ validationScheme: z.string().min(3, 'Username must be at least 3 characters')
163
+ }
164
+ ```
165
+
166
+ #### EMAIL Field
167
+ ```tsx
168
+ {
169
+ name: 'email',
170
+ type: FormFieldTypes.EMAIL,
171
+ label: 'Email Address',
172
+ placeholder: 'you@example.com',
173
+ required: true,
174
+ width: FormFieldWidth.FULL,
175
+ validationScheme: z.string().email('Invalid email address')
176
+ }
177
+ ```
178
+
179
+ #### PASSWORD Field
180
+ ```tsx
181
+ {
182
+ name: 'password',
183
+ type: FormFieldTypes.PASSWORD,
184
+ label: 'Password',
185
+ placeholder: 'Create a strong password',
186
+ required: true,
187
+ width: FormFieldWidth.FULL,
188
+ validationScheme: z.string()
189
+ .min(6, 'Password must be at least 6 characters')
190
+ .max(36, 'Password too long')
191
+ }
192
+ ```
193
+
194
+ #### NUMBER Field
195
+ ```tsx
196
+ {
197
+ name: 'age',
198
+ type: FormFieldTypes.NUMBER,
199
+ label: 'Age',
200
+ placeholder: 'Enter your age',
201
+ min: 18,
202
+ max: 100,
203
+ required: true,
204
+ width: FormFieldWidth.HALF,
205
+ validationScheme: z.number().min(18, 'Must be 18 or older')
206
+ }
207
+ ```
208
+
209
+ #### DATE Field
210
+ ```tsx
211
+ {
212
+ name: 'birthDate',
213
+ type: FormFieldTypes.DATE,
214
+ label: 'Date of Birth',
215
+ required: true,
216
+ width: FormFieldWidth.HALF,
217
+ validationScheme: z.string().refine(
218
+ (date) => new Date(date) < new Date(),
219
+ 'Date must be in the past'
220
+ )
221
+ }
222
+ ```
223
+
224
+ ### Composite Fields
225
+
226
+ #### SELECT Field
227
+ ```tsx
228
+ {
229
+ name: 'country',
230
+ type: FormFieldTypes.SELECT,
231
+ label: 'Country',
232
+ required: true,
233
+ width: FormFieldWidth.FULL,
234
+ options: [
235
+ { label: 'United States', value: 'US' },
236
+ { label: 'Canada', value: 'CA' },
237
+ { label: 'United Kingdom', value: 'GB' },
238
+ { label: 'Australia', value: 'AU' }
239
+ ],
240
+ validationScheme: z.string().min(1, 'Please select a country')
241
+ }
242
+ ```
243
+
244
+ #### RADIO Field
245
+ ```tsx
246
+ {
247
+ name: 'accountType',
248
+ type: FormFieldTypes.RADIO,
249
+ label: 'Account Type',
250
+ required: true,
251
+ width: FormFieldWidth.FULL,
252
+ options: [
253
+ { label: 'Personal', value: 'personal' },
254
+ { label: 'Business', value: 'business' },
255
+ { label: 'Enterprise', value: 'enterprise' }
256
+ ],
257
+ validationScheme: z.string().min(1, 'Please select an account type')
258
+ }
259
+ ```
260
+
261
+ #### CHECKBOX Field
262
+ ```tsx
263
+ {
264
+ name: 'interests',
265
+ type: FormFieldTypes.CHECKBOX,
266
+ label: 'Interests',
267
+ width: FormFieldWidth.FULL,
268
+ options: [
269
+ { label: 'Technology', value: 'tech' },
270
+ { label: 'Finance', value: 'finance' },
271
+ { label: 'Health', value: 'health' },
272
+ { label: 'Travel', value: 'travel' }
273
+ ],
274
+ validationScheme: z.array(z.string()).min(1, 'Select at least one interest')
275
+ }
276
+ ```
277
+
278
+ #### TEXTAREA Field
279
+ ```tsx
280
+ {
281
+ name: 'bio',
282
+ type: FormFieldTypes.TEXTAREA,
283
+ label: 'Bio',
284
+ placeholder: 'Tell us about yourself',
285
+ width: FormFieldWidth.FULL,
286
+ validationScheme: z.string()
287
+ .min(10, 'Bio must be at least 10 characters')
288
+ .max(500, 'Bio must not exceed 500 characters')
289
+ }
290
+ ```
291
+
292
+ #### FILE Field
293
+ ```tsx
294
+ {
295
+ name: 'profilePhoto',
296
+ type: FormFieldTypes.FILE,
297
+ label: 'Profile Photo',
298
+ width: FormFieldWidth.FULL,
299
+ validationScheme: z.any()
300
+ }
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Validation
306
+
307
+ N8x Form Utils uses **Zod** for type-safe schema validation. All fields support a `validationScheme` property where you define your validation rules.
308
+
309
+ ### Basic Validation Examples
310
+
311
+ ```tsx
312
+ import { z } from 'n8x-form-utils'
313
+
314
+ // Email validation
315
+ validationScheme: z.string().email('Invalid email address')
316
+
317
+ // Required string with minimum length
318
+ validationScheme: z.string().min(3, 'Minimum 3 characters required')
319
+
320
+ // Number with range
321
+ validationScheme: z.number().min(0).max(100, 'Value must be between 0 and 100')
322
+
323
+ // Custom refine validation
324
+ validationScheme: z.string().refine(
325
+ (value) => value !== 'admin',
326
+ 'Username cannot be "admin"'
327
+ )
328
+
329
+ // Conditional validation
330
+ validationScheme: z.string().or(z.number())
331
+
332
+ // Array validation
333
+ validationScheme: z.array(z.string()).min(1, 'Select at least one option')
334
+ ```
335
+
336
+ ### Complete Registration Form with Validation
337
+
338
+ ```tsx
339
+ // forms/register.ts
340
+ import { z } from 'n8x-form-utils'
341
+ import { FormFieldTypes, FormFieldWidth, type FormFields } from 'n8x-form-utils'
342
+
343
+ export const registerForm: FormFields[] = [
344
+ {
345
+ name: 'email',
346
+ type: FormFieldTypes.EMAIL,
347
+ label: 'Email',
348
+ placeholder: 'hello@example.com',
349
+ required: true,
350
+ width: FormFieldWidth.FULL,
351
+ validationScheme: z.string().email('Please enter a valid email')
352
+ },
353
+ {
354
+ name: 'password',
355
+ type: FormFieldTypes.PASSWORD,
356
+ label: 'Password',
357
+ placeholder: 'Create a strong password',
358
+ required: true,
359
+ width: FormFieldWidth.FULL,
360
+ validationScheme: z.string()
361
+ .min(6, 'Password must be at least 6 characters')
362
+ .max(36, 'Password too long')
363
+ },
364
+ {
365
+ name: 'confirmPassword',
366
+ type: FormFieldTypes.PASSWORD,
367
+ label: 'Confirm Password',
368
+ placeholder: 'Re-enter your password',
369
+ required: true,
370
+ width: FormFieldWidth.FULL,
371
+ validationScheme: z.string().min(6, 'Password must be at least 6 characters')
372
+ },
373
+ {
374
+ name: 'fullName',
375
+ type: FormFieldTypes.TEXT,
376
+ label: 'Full Name',
377
+ placeholder: 'John Doe',
378
+ required: true,
379
+ width: FormFieldWidth.FULL,
380
+ validationScheme: z.string().min(2, 'Name must be at least 2 characters')
381
+ },
382
+ {
383
+ name: 'agreeToTerms',
384
+ type: FormFieldTypes.CHECKBOX,
385
+ label: 'Terms',
386
+ width: FormFieldWidth.FULL,
387
+ options: [
388
+ { label: 'I agree to the Terms and Conditions', value: 'agree' }
389
+ ],
390
+ validationScheme: z.array(z.string()).min(1, 'You must agree to the terms')
391
+ }
392
+ ]
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Styling Themes
398
+
399
+ N8x Form Utils provides 4 pre-built styling themes via `EDefaultFieldStyles`. You can apply a theme through the `defaultFieldStyles` prop in the N8xForm component.
400
+
401
+ ### Available Themes
402
+
403
+ #### CLEAN (Minimal, professional)
404
+ ```tsx
405
+ <N8xForm
406
+ fields={loginForm}
407
+ defaultFieldStyles={EDefaultFieldStyles.CLEAN}
408
+ onShubmit={handleSubmit}
409
+ />
410
+ ```
411
+ Clean white inputs with thin borders, subtle focus effects, and a professional appearance.
412
+
413
+ #### SOFT_GLASS (Modern, glassmorphism)
414
+ ```tsx
415
+ <N8xForm
416
+ fields={loginForm}
417
+ defaultFieldStyles={EDefaultFieldStyles.SOFT_GLASS}
418
+ onShubmit={handleSubmit}
419
+ />
420
+ ```
421
+ Soft, frosted appearance with light background and smooth transitions. Default theme.
422
+
423
+ #### DARK (Dark mode)
424
+ ```tsx
425
+ <N8xForm
426
+ fields={loginForm}
427
+ defaultFieldStyles={EDefaultFieldStyles.DARK}
428
+ onShubmit={handleSubmit}
429
+ />
430
+ ```
431
+ Dark background inputs suitable for dark mode interfaces.
432
+
433
+ #### FLOATING_LABEL (Material Design)
434
+ ```tsx
435
+ <N8xForm
436
+ fields={loginForm}
437
+ defaultFieldStyles={EDefaultFieldStyles.FLOATING_LABEL}
438
+ onShubmit={handleSubmit}
439
+ />
440
+ ```
441
+ Floating label style with animated labels on focus/input. Material Design inspired.
442
+
443
+ ### Custom Styling
444
+
445
+ You can also apply custom Tailwind CSS classes to individual fields:
446
+
447
+ ```tsx
448
+ {
449
+ name: 'customField',
450
+ type: FormFieldTypes.TEXT,
451
+ label: 'Custom Styled',
452
+ _className: 'bg-gradient-to-r from-blue-400 to-blue-600 text-white rounded-xl'
453
+ }
454
+ ```
455
+
456
+ Or pass a custom string:
457
+
458
+ ```tsx
459
+ <N8xForm
460
+ fields={loginForm}
461
+ defaultFieldStyles="custom-class-string"
462
+ onShubmit={handleSubmit}
463
+ />
464
+ ```
465
+
466
+ ---
467
+
468
+ ## Grouped Forms
469
+
470
+ For complex forms with multiple sections, organize fields into logical groups. Grouped forms automatically render section headers.
471
+
472
+ ### Example: User Profile Form with Groups
473
+
474
+ ```tsx
475
+ // forms/userProfile.ts
476
+ import { z } from 'n8x-form-utils'
477
+ import { FormFieldTypes, FormFieldWidth, type FormFields } from 'n8x-form-utils'
478
+
479
+ export const userProfileForm = {
480
+ 'Personal Information': [
481
+ {
482
+ name: 'firstName',
483
+ type: FormFieldTypes.TEXT,
484
+ label: 'First Name',
485
+ required: true,
486
+ width: FormFieldWidth.HALF,
487
+ validationScheme: z.string().min(2, 'First name is required')
488
+ },
489
+ {
490
+ name: 'lastName',
491
+ type: FormFieldTypes.TEXT,
492
+ label: 'Last Name',
493
+ required: true,
494
+ width: FormFieldWidth.HALF,
495
+ validationScheme: z.string().min(2, 'Last name is required')
496
+ },
497
+ {
498
+ name: 'email',
499
+ type: FormFieldTypes.EMAIL,
500
+ label: 'Email Address',
501
+ required: true,
502
+ width: FormFieldWidth.FULL,
503
+ validationScheme: z.string().email('Invalid email')
504
+ },
505
+ {
506
+ name: 'birthDate',
507
+ type: FormFieldTypes.DATE,
508
+ label: 'Date of Birth',
509
+ required: false,
510
+ width: FormFieldWidth.HALF,
511
+ validationScheme: z.string().optional()
512
+ }
513
+ ],
514
+ 'Contact Information': [
515
+ {
516
+ name: 'phone',
517
+ type: FormFieldTypes.TEXT,
518
+ label: 'Phone Number',
519
+ placeholder: '+1 (555) 123-4567',
520
+ width: FormFieldWidth.FULL,
521
+ validationScheme: z.string().optional()
522
+ },
523
+ {
524
+ name: 'country',
525
+ type: FormFieldTypes.SELECT,
526
+ label: 'Country',
527
+ required: true,
528
+ width: FormFieldWidth.HALF,
529
+ options: [
530
+ { label: 'United States', value: 'US' },
531
+ { label: 'Canada', value: 'CA' },
532
+ { label: 'United Kingdom', value: 'GB' }
533
+ ],
534
+ validationScheme: z.string().min(1, 'Select a country')
535
+ },
536
+ {
537
+ name: 'city',
538
+ type: FormFieldTypes.TEXT,
539
+ label: 'City',
540
+ required: true,
541
+ width: FormFieldWidth.HALF,
542
+ validationScheme: z.string().min(2, 'City is required')
543
+ }
544
+ ],
545
+ 'Preferences': [
546
+ {
547
+ name: 'language',
548
+ type: FormFieldTypes.SELECT,
549
+ label: 'Preferred Language',
550
+ width: FormFieldWidth.FULL,
551
+ options: [
552
+ { label: 'English', value: 'en' },
553
+ { label: 'Spanish', value: 'es' },
554
+ { label: 'French', value: 'fr' },
555
+ { label: 'German', value: 'de' }
556
+ ],
557
+ validationScheme: z.string().optional()
558
+ },
559
+ {
560
+ name: 'newsletter',
561
+ type: FormFieldTypes.CHECKBOX,
562
+ label: 'Email Preferences',
563
+ width: FormFieldWidth.FULL,
564
+ options: [
565
+ { label: 'Subscribe to newsletter', value: 'newsletter' },
566
+ { label: 'Receive promotional emails', value: 'promo' }
567
+ ],
568
+ validationScheme: z.array(z.string()).optional()
569
+ }
570
+ ]
571
+ }
572
+ ```
573
+
574
+ ### Using Grouped Forms
575
+
576
+ ```tsx
577
+ // pages/profile/UserProfile.tsx
578
+ import { useContext } from 'react'
579
+ import { N8xForm, useN8xFormQuery, EDefaultFieldStyles } from 'n8x-form-utils'
580
+ import { userProfileForm } from '../../forms/userProfile'
581
+ import { updateUserProfile } from '../../service/user-service'
582
+
583
+ export default function UserProfilePage() {
584
+ const { data, execute, error, isLoading } = useN8xFormQuery(updateUserProfile)
585
+
586
+ const handleProfileUpdate = async (formData: any) => {
587
+ execute(formData)
588
+ }
589
+
590
+ return (
591
+ <div className="w-full p-6">
592
+ <h1 className="text-3xl font-bold mb-6">Edit Profile</h1>
593
+
594
+ <N8xForm
595
+ fields={userProfileForm}
596
+ onShubmit={handleProfileUpdate}
597
+ defaultFieldStyles={EDefaultFieldStyles.SOFT_GLASS}
598
+ >
599
+ <button
600
+ disabled={isLoading}
601
+ type="submit"
602
+ className="col-span-4 bg-blue-500 disabled:bg-zinc-400 text-white py-2 px-4 rounded font-medium"
603
+ >
604
+ {isLoading ? 'Saving...' : 'Save Changes'}
605
+ </button>
606
+ </N8xForm>
607
+
608
+ {error && <p className="text-red-500 mt-4">{error}</p>}
609
+ </div>
610
+ )
611
+ }
612
+ ```
613
+
614
+ ---
615
+
616
+ ## Form Submission
617
+
618
+ ### useN8xFormQuery Hook
619
+
620
+ Execute async operations when the form is submitted. Perfect for API calls.
621
+
622
+ ```tsx
623
+ import { useN8xFormQuery } from 'n8x-form-utils'
624
+ import { login } from '../service/auth-service'
625
+
626
+ export default function LoginComponent() {
627
+ const { data, execute, error, isLoading } = useN8xFormQuery(login)
628
+
629
+ const handleSubmit = async (formData: any) => {
630
+ execute(formData) // Calls the login service
631
+ }
632
+
633
+ return (
634
+ <N8xForm
635
+ fields={loginForm}
636
+ onShubmit={handleSubmit}
637
+ >
638
+ <button disabled={isLoading} type="submit">
639
+ {isLoading ? 'Loading...' : 'Login'}
640
+ </button>
641
+ </N8xForm>
642
+ )
643
+ }
644
+ ```
645
+
646
+ ### Hook Signature
647
+
648
+ ```tsx
649
+ const { data, execute, error, isLoading } = useN8xFormQuery(
650
+ queryService: (payload: any, signal?: AbortSignal) => Promise<any>
651
+ )
652
+ ```
653
+
654
+ **Properties:**
655
+ - `data`: Response data from the service after successful execution
656
+ - `execute`: Function to call the service with form payload
657
+ - `error`: Error message if the request fails
658
+ - `isLoading`: Boolean indicating if the request is in progress
659
+
660
+ ### Service Function Example
661
+
662
+ ```tsx
663
+ // service/auth-service.ts
664
+ import axios from 'axios'
665
+
666
+ export const login = async (payload: any, signal?: AbortSignal) => {
667
+ return axios.post('/api/auth/login', payload, { signal })
668
+ }
669
+
670
+ export const register = async (payload: any, signal?: AbortSignal) => {
671
+ return axios.post('/api/auth/register', payload, { signal })
672
+ }
673
+ ```
674
+
675
+ ### Complete Submission Example with Side Effects
676
+
677
+ ```tsx
678
+ import { useContext, useEffect } from 'react'
679
+ import { useNavigate } from 'react-router-dom'
680
+ import { N8xForm, useN8xFormQuery, EDefaultFieldStyles } from 'n8x-form-utils'
681
+ import { AuthContext } from '../context/AuthContext'
682
+ import { loginForm } from '../forms/login'
683
+ import { login } from '../service/auth-service'
684
+
685
+ export default function LoginPage() {
686
+ const { setUser } = useContext(AuthContext)
687
+ const navigate = useNavigate()
688
+ const { data, execute, error, isLoading } = useN8xFormQuery(login)
689
+
690
+ const handleLoginSubmit = async (formData: any) => {
691
+ execute(formData)
692
+ }
693
+
694
+ // Handle successful login
695
+ useEffect(() => {
696
+ if (data) {
697
+ setUser(data)
698
+ navigate('/home')
699
+ }
700
+ }, [data, setUser, navigate])
701
+
702
+ return (
703
+ <div className="min-h-screen flex items-center justify-center">
704
+ <div className="w-full max-w-md">
705
+ <h1 className="text-4xl font-bold mb-6">Login</h1>
706
+
707
+ <N8xForm
708
+ fields={loginForm}
709
+ onShubmit={handleLoginSubmit}
710
+ defaultFieldStyles={EDefaultFieldStyles.FLOATING_LABEL}
711
+ >
712
+ <button
713
+ disabled={isLoading}
714
+ type="submit"
715
+ className="col-span-4 bg-blue-500 hover:bg-blue-600 disabled:bg-zinc-400 text-white py-2 rounded font-medium transition"
716
+ >
717
+ {isLoading ? 'Logging in...' : 'Login'}
718
+ </button>
719
+ </N8xForm>
720
+
721
+ {error && <p className="text-red-500 text-center mt-4">{error}</p>}
722
+ </div>
723
+ </div>
724
+ )
725
+ }
726
+ ```
727
+
728
+ ---
729
+
730
+ ## Data Fetching Hooks
731
+
732
+ ### useN8xQuery Hook
733
+
734
+ Auto-execute queries on component mount. Use this for fetching initial data to populate forms.
735
+
736
+ ```tsx
737
+ import { useN8xQuery } from 'n8x-form-utils'
738
+ import { fetchUserProfile } from '../service/user-service'
739
+
740
+ export default function ProfilePage() {
741
+ const { data: profile, error, isLoading } = useN8xQuery(fetchUserProfile, userId)
742
+
743
+ if (isLoading) return <div>Loading profile...</div>
744
+ if (error) return <div>Error: {error}</div>
745
+
746
+ return (
747
+ <N8xForm
748
+ fields={userProfileForm}
749
+ onShubmit={handleProfileUpdate}
750
+ />
751
+ )
752
+ }
753
+ ```
754
+
755
+ ### Hook Signature
756
+
757
+ ```tsx
758
+ const { data, error, isLoading } = useN8xQuery(
759
+ queryService: (payload: any, signal?: AbortSignal) => Promise<any>,
760
+ ...queryParams: any[]
761
+ )
762
+ ```
763
+
764
+ **Properties:**
765
+ - `data`: Response data from the service
766
+ - `error`: Error message if the request fails
767
+ - `isLoading`: Boolean indicating if the request is loading
768
+
769
+ ### Key Differences
770
+
771
+ | Feature | useN8xFormQuery | useN8xQuery |
772
+ |---------|-----------------|------------|
773
+ | Runs on mount | ❌ No | ✅ Yes |
774
+ | Triggered by | Manual call | Auto on mount |
775
+ | Return state | `data, error, isLoading, execute` | `data, error, isLoading` |
776
+ | Use case | Form submissions | Initial data fetch |
777
+
778
+ ---
779
+
780
+ ## Error Handling
781
+
782
+ ### Display Validation Errors
783
+
784
+ Validation errors from Zod are automatically displayed below each field with error styling.
785
+
786
+ ```tsx
787
+ <N8xForm
788
+ fields={loginForm}
789
+ onShubmit={handleSubmit}
790
+ />
791
+ // Errors appear in real-time as user types (default: "onChange" mode)
792
+ ```
793
+
794
+ ### Display API Errors
795
+
796
+ Handle errors returned from `useN8xFormQuery`:
797
+
798
+ ```tsx
799
+ const { data, execute, error, isLoading } = useN8xFormQuery(login)
800
+
801
+ return (
802
+ <>
803
+ <N8xForm
804
+ fields={loginForm}
805
+ onShubmit={handleSubmit}
806
+ />
807
+
808
+ {error && (
809
+ <div className="mt-4 p-4 bg-red-50 border border-red-200 rounded">
810
+ <p className="text-red-700 font-semibold">Login failed</p>
811
+ <p className="text-red-600 text-sm">{error}</p>
812
+ </div>
813
+ )}
814
+ </>
815
+ )
816
+ ```
817
+
818
+ ### Custom Error Component
819
+
820
+ ```tsx
821
+ interface ErrorProps {
822
+ message: string
823
+ onDismiss?: () => void
824
+ }
825
+
826
+ function ErrorAlert({ message, onDismiss }: ErrorProps) {
827
+ return (
828
+ <div className="p-4 bg-red-50 border-l-4 border-red-500 rounded">
829
+ <p className="text-red-700">{message}</p>
830
+ {onDismiss && (
831
+ <button onClick={onDismiss} className="mt-2 text-sm text-red-600">
832
+ Dismiss
833
+ </button>
834
+ )}
835
+ </div>
836
+ )
837
+ }
838
+
839
+ export default function LoginPage() {
840
+ const { data, execute, error, isLoading } = useN8xFormQuery(login)
841
+ const [dismissError, setDismissError] = React.useState(false)
842
+
843
+ return (
844
+ <>
845
+ {error && !dismissError && (
846
+ <ErrorAlert
847
+ message={error}
848
+ onDismiss={() => setDismissError(true)}
849
+ />
850
+ )}
851
+ <N8xForm
852
+ fields={loginForm}
853
+ onShubmit={(data) => {
854
+ setDismissError(false)
855
+ execute(data)
856
+ }}
857
+ />
858
+ </>
859
+ )
860
+ }
861
+ ```
862
+
863
+ ### Error Recovery
864
+
865
+ ```tsx
866
+ const [errorKey, setErrorKey] = React.useState(0)
867
+
868
+ const handleRetry = () => {
869
+ setErrorKey(prev => prev + 1) // Force form re-render
870
+ }
871
+
872
+ return (
873
+ <>
874
+ {error && (
875
+ <button onClick={handleRetry} className="text-blue-600">
876
+ Retry
877
+ </button>
878
+ )}
879
+ <N8xForm
880
+ key={errorKey}
881
+ fields={loginForm}
882
+ onShubmit={handleSubmit}
883
+ />
884
+ </>
885
+ )
886
+ ```
887
+
888
+ ---
889
+
890
+ ## API Reference
891
+
892
+ ### N8xForm Component
893
+
894
+ The main form component that renders fields and handles submission.
895
+
896
+ ```tsx
897
+ interface FormProps {
898
+ fields?: FormFields[] | GroupedFormFields
899
+ defaultFieldStyles?: EDefaultFieldStyles | string
900
+ onShubmit: (data: any) => void
901
+ mode?: Mode
902
+ children?: React.ReactNode
903
+ }
904
+
905
+ <N8xForm
906
+ fields={loginForm}
907
+ defaultFieldStyles={EDefaultFieldStyles.FLOATING_LABEL}
908
+ onShubmit={handleSubmit}
909
+ mode="onBlur"
910
+ >
911
+ <button type="submit">Submit</button>
912
+ </N8xForm>
913
+ ```
914
+
915
+ **Props:**
916
+ - `fields`: Array or object of FormFields to render
917
+ - `defaultFieldStyles`: Theme from EDefaultFieldStyles or custom Tailwind string
918
+ - `onShubmit`: Callback when form is successfully submitted
919
+ - `mode`: React Hook Form validation mode ('onChange', 'onBlur', 'onTouched', 'onSubmit', 'all')
920
+ - `children`: Additional JSX elements (e.g., submit button, custom elements)
921
+
922
+ ### FormFields Type
923
+
924
+ Configuration for a single form field.
925
+
926
+ ```tsx
927
+ type FormFields = {
928
+ name: string // Unique field identifier
929
+ type: FormFieldTypes // Input type
930
+ validationScheme?: ZodTypeAny // Zod validation schema
931
+ label?: string // Display label
932
+ placeholder?: string // Input placeholder
933
+ options?: { label: string; value: string }[] // For select/radio/checkbox
934
+ min?: number // Min value for number fields
935
+ max?: number // Max value for number fields
936
+ required?: boolean // Is field required?
937
+ width?: FormFieldWidth // Grid column span
938
+ watch?: boolean // Watch for changes
939
+ default?: string // Default value
940
+ _className?: string // Custom Tailwind classes
941
+ }
942
+ ```
943
+
944
+ ### FormFieldTypes Enum
945
+
946
+ ```tsx
947
+ enum FormFieldTypes {
948
+ TEXT = 'text'
949
+ NUMBER = 'number'
950
+ EMAIL = 'email'
951
+ PASSWORD = 'password'
952
+ SELECT = 'select'
953
+ RADIO = 'radio'
954
+ CHECKBOX = 'checkbox'
955
+ TEXTAREA = 'textarea'
956
+ DATE = 'date'
957
+ FILE = 'file'
958
+ SUBMIT = 'submit'
959
+ }
960
+ ```
961
+
962
+ ### FormFieldWidth Enum
963
+
964
+ ```tsx
965
+ enum FormFieldWidth {
966
+ FULL = 'col-span-4' // 100% width
967
+ HALF = 'col-span-2' // 50% width
968
+ THIRD = 'col-span-3' // 75% width
969
+ QUARTER = 'col-span-1' // 25% width
970
+ }
971
+ ```
972
+
973
+ ### EDefaultFieldStyles Enum
974
+
975
+ ```tsx
976
+ enum EDefaultFieldStyles {
977
+ CLEAN // Minimal white inputs
978
+ SOFT_GLASS // Glassmorphism (default)
979
+ DARK // Dark mode inputs
980
+ FLOATING_LABEL // Material Design floating labels
981
+ }
982
+ ```
983
+
984
+ ### useN8xFormQuery Hook
985
+
986
+ ```tsx
987
+ const {
988
+ data, // Response data after successful request
989
+ execute, // Function to execute the query
990
+ error, // Error message string or null
991
+ isLoading // Boolean loading state
992
+ } = useN8xFormQuery(
993
+ queryService: (payload: any, signal?: AbortSignal) => Promise<any>
994
+ )
995
+ ```
996
+
997
+ **Example:**
998
+ ```tsx
999
+ const { data, execute, error, isLoading } = useN8xFormQuery(login)
1000
+
1001
+ // Later in form submission:
1002
+ const handleSubmit = (formData: any) => {
1003
+ execute(formData)
1004
+ }
1005
+ ```
1006
+
1007
+ ### useN8xQuery Hook
1008
+
1009
+ ```tsx
1010
+ const {
1011
+ data, // Response data after successful request
1012
+ error, // Error message string or null
1013
+ isLoading // Boolean loading state
1014
+ } = useN8xQuery(
1015
+ queryService: (payload: any, signal?: AbortSignal) => Promise<any>,
1016
+ ...queryParams: any[]
1017
+ )
1018
+ ```
1019
+
1020
+ **Example:**
1021
+ ```tsx
1022
+ const { data, error, isLoading } = useN8xQuery(fetchUserProfile, userId)
1023
+ ```
1024
+
1025
+ ### FormContextType Interface
1026
+
1027
+ ```tsx
1028
+ interface FormContextType {
1029
+ formUtils: ReturnType<typeof useForm> // React Hook Form utilities
1030
+ setValidationSchema: (props: ZodSchema) => void // Update validation schema
1031
+ setMode: (props: Mode) => void // Update validation mode
1032
+ }
1033
+ ```
1034
+
1035
+ ---
1036
+
1037
+ ## Complete Real-World Example
1038
+
1039
+ Here's a complete registration flow combining everything:
1040
+
1041
+ ```tsx
1042
+ // forms/registration.ts
1043
+ import { z } from 'n8x-form-utils'
1044
+ import { FormFieldTypes, FormFieldWidth, type FormFields } from 'n8x-form-utils'
1045
+
1046
+ export const registrationForm: FormFields[] = [
1047
+ {
1048
+ name: 'email',
1049
+ type: FormFieldTypes.EMAIL,
1050
+ label: 'Email Address',
1051
+ placeholder: 'hello@example.com',
1052
+ required: true,
1053
+ width: FormFieldWidth.FULL,
1054
+ validationScheme: z.string().email('Invalid email address')
1055
+ },
1056
+ {
1057
+ name: 'password',
1058
+ type: FormFieldTypes.PASSWORD,
1059
+ label: 'Password',
1060
+ placeholder: 'Create a strong password',
1061
+ required: true,
1062
+ width: FormFieldWidth.FULL,
1063
+ validationScheme: z.string()
1064
+ .min(6, 'Password must be at least 6 characters')
1065
+ .max(36, 'Password too long')
1066
+ },
1067
+ {
1068
+ name: 'confirmPassword',
1069
+ type: FormFieldTypes.PASSWORD,
1070
+ label: 'Confirm Password',
1071
+ placeholder: 'Re-enter your password',
1072
+ required: true,
1073
+ width: FormFieldWidth.FULL,
1074
+ validationScheme: z.string()
1075
+ .min(6, 'Password must match')
1076
+ },
1077
+ {
1078
+ name: 'fullName',
1079
+ type: FormFieldTypes.TEXT,
1080
+ label: 'Full Name',
1081
+ placeholder: 'John Doe',
1082
+ required: true,
1083
+ width: FormFieldWidth.FULL,
1084
+ validationScheme: z.string().min(2, 'Name too short')
1085
+ },
1086
+ {
1087
+ name: 'accountType',
1088
+ type: FormFieldTypes.RADIO,
1089
+ label: 'Account Type',
1090
+ required: true,
1091
+ width: FormFieldWidth.FULL,
1092
+ options: [
1093
+ { label: 'Personal', value: 'personal' },
1094
+ { label: 'Business', value: 'business' }
1095
+ ],
1096
+ validationScheme: z.string()
1097
+ },
1098
+ {
1099
+ name: 'interests',
1100
+ type: FormFieldTypes.CHECKBOX,
1101
+ label: 'Select Your Interests',
1102
+ width: FormFieldWidth.FULL,
1103
+ options: [
1104
+ { label: 'Technology', value: 'tech' },
1105
+ { label: 'Finance', value: 'finance' },
1106
+ { label: 'Health', value: 'health' }
1107
+ ],
1108
+ validationScheme: z.array(z.string()).min(1, 'Select at least one interest')
1109
+ },
1110
+ {
1111
+ name: 'terms',
1112
+ type: FormFieldTypes.CHECKBOX,
1113
+ label: 'Agreement',
1114
+ required: true,
1115
+ width: FormFieldWidth.FULL,
1116
+ options: [
1117
+ { label: 'I agree to Terms and Conditions', value: 'agreed' }
1118
+ ],
1119
+ validationScheme: z.array(z.string()).min(1, 'You must agree to terms')
1120
+ }
1121
+ ]
1122
+
1123
+ // pages/auth/Register.tsx
1124
+ import { useContext, useEffect, useState } from 'react'
1125
+ import { Link, useNavigate } from 'react-router-dom'
1126
+ import { N8xForm, useN8xFormQuery, EDefaultFieldStyles } from 'n8x-form-utils'
1127
+ import { AuthContext, type IAuthContext } from '../../context/AuthContext/context'
1128
+ import { registrationForm } from '../../forms/registration'
1129
+ import { register } from '../../service/auth-service'
1130
+
1131
+ export default function RegisterPage() {
1132
+ const { user, setUser } = useContext(AuthContext) as IAuthContext
1133
+ const navigate = useNavigate()
1134
+ const [dismissError, setDismissError] = useState(false)
1135
+ const { data, execute, error, isLoading } = useN8xFormQuery(register)
1136
+
1137
+ const handleRegisterSubmit = async (formData: any) => {
1138
+ // Validate passwords match
1139
+ if (formData.password !== formData.confirmPassword) {
1140
+ // You could set a custom error here or let Zod handle it
1141
+ return
1142
+ }
1143
+ setDismissError(false)
1144
+ execute(formData)
1145
+ }
1146
+
1147
+ // Handle successful registration
1148
+ useEffect(() => {
1149
+ if (data) {
1150
+ setUser(data)
1151
+ navigate('/home')
1152
+ }
1153
+ }, [data, setUser, navigate])
1154
+
1155
+ // Redirect if already logged in
1156
+ useEffect(() => {
1157
+ if (user) {
1158
+ navigate('/home')
1159
+ }
1160
+ }, [user, navigate])
1161
+
1162
+ return (
1163
+ <div className="w-full min-h-screen flex justify-center items-center bg-gradient-to-br from-zinc-50 to-zinc-100 p-4">
1164
+ <div className="md:w-[600px] w-full">
1165
+ <div className="mb-8">
1166
+ <h1 className="text-4xl font-bold tracking-tight mb-2">Create Your Account</h1>
1167
+ <p className="text-zinc-600">Join us today and get started</p>
1168
+ </div>
1169
+
1170
+ {error && !dismissError && (
1171
+ <div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
1172
+ <p className="text-red-700 font-semibold">Registration failed</p>
1173
+ <p className="text-red-600 text-sm mt-1">{error}</p>
1174
+ <button
1175
+ onClick={() => setDismissError(true)}
1176
+ className="mt-2 text-xs text-red-600 hover:text-red-700"
1177
+ >
1178
+ Dismiss
1179
+ </button>
1180
+ </div>
1181
+ )}
1182
+
1183
+ <N8xForm
1184
+ fields={registrationForm}
1185
+ onShubmit={handleRegisterSubmit}
1186
+ defaultFieldStyles={EDefaultFieldStyles.FLOATING_LABEL}
1187
+ >
1188
+ <button
1189
+ disabled={isLoading}
1190
+ type="submit"
1191
+ className={`col-span-4 py-3 px-4 rounded-lg font-semibold text-white transition-all ${
1192
+ isLoading
1193
+ ? 'bg-zinc-400 cursor-not-allowed'
1194
+ : 'bg-blue-500 hover:bg-blue-600 active:bg-blue-700'
1195
+ }`}
1196
+ >
1197
+ {isLoading ? (
1198
+ <span className="inline-flex items-center gap-2">
1199
+ <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
1200
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
1201
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
1202
+ </svg>
1203
+ Creating account...
1204
+ </span>
1205
+ ) : (
1206
+ 'Create Account'
1207
+ )}
1208
+ </button>
1209
+ </N8xForm>
1210
+
1211
+ <p className="text-center text-sm text-zinc-600 mt-6">
1212
+ Already have an account?{' '}
1213
+ <Link to="/login" className="text-blue-500 hover:text-blue-600 font-semibold">
1214
+ Login here
1215
+ </Link>
1216
+ </p>
1217
+ </div>
1218
+ </div>
1219
+ )
1220
+ }
1221
+ ```
1222
+
1223
+ ---
1224
+
1225
+ ## Browser Support
1226
+
1227
+ - Chrome (latest)
1228
+ - Firefox (latest)
1229
+ - Safari (latest)
1230
+ - Edge (latest)
1231
+
1232
+ ## License
1233
+
1234
+ ISC License - Created by Syed Shayan Ali
1235
+
1236
+ ## Contributing
1237
+
1238
+ This project is currently not open for external contributions. It will be made open source very soon in the future once it has matured and gained wider adoption.