@ilife-tech/react-application-flow-renderer 1.0.8

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,1679 @@
1
+ # React Dynamic Form Renderer
2
+
3
+ A powerful and flexible React package for rendering dynamic forms with advanced validation, conditional logic, and third-party API integration capabilities.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@ilife-tech/react-application-flow-renderer.svg)](https://www.npmjs.com/package/@ilife-tech/react-application-flow-renderer)
6
+ [![license](https://img.shields.io/npm/l/@ilife-tech/react-application-flow-renderer.svg)](https://github.com/ilife-technologies/react-dynamic-form-renderer/blob/main/LICENSE)
7
+
8
+ ## Table of Contents
9
+
10
+ - [Features](#features)
11
+ - [Prerequisites](#prerequisites)
12
+ - [Installation](#installation)
13
+ - [Quick Start](#quick-start)
14
+ - [Form Schema Structure](#form-schema-structure)
15
+ - [API Integration](#api-integration)
16
+ - [Props Reference](#props-reference)
17
+ - [Field Types](#field-types)
18
+ - [Advanced Configuration](#advanced-configuration)
19
+ - [Troubleshooting](#troubleshooting)
20
+ - [Examples & Best Practices](#examples--best-practices)
21
+
22
+ ## Features
23
+
24
+ ### Core Capabilities
25
+
26
+ - **Schema-Driven Forms:** Generate complex forms from JSON schema
27
+ - **Dynamic Validation:** Client and server-side validation support
28
+ - **Conditional Logic:** Show/hide fields based on advanced rule conditions
29
+ - **Third-Party API Integration:** Seamless integration with external APIs
30
+ - **Nested Forms:** Support for complex, nested form structures
31
+ - **Responsive Design:** Mobile-friendly with customizable breakpoints
32
+ - **Accessibility:** WCAG 2.1 compliant components
33
+
34
+ ## Prerequisites
35
+
36
+ - React 18+
37
+ - Node.js 20+
38
+ - npm 10+ or Yarn
39
+ - Browser support: Latest versions of Chrome, Firefox, Safari, and Edge
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ # Using npm
45
+ npm install @ilife-tech/react-application-flow-renderer
46
+
47
+ # Using yarn
48
+ yarn add @ilife-tech/react-application-flow-renderer
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ### Basic Implementation
54
+
55
+ ```jsx
56
+ import React, { useState } from 'react';
57
+ import DynamicForm from '@ilife-tech/react-application-flow-renderer';
58
+
59
+ const App = () => {
60
+ // Form schema definition
61
+ const questionGroups = [
62
+ {
63
+ groupId: 'personalInfo',
64
+ groupName: 'Personal Information',
65
+ questions: [
66
+ {
67
+ questionId: 'fullName',
68
+ questionType: 'text',
69
+ label: 'Full Name',
70
+ validation: { required: true },
71
+ },
72
+ {
73
+ questionId: 'email',
74
+ questionType: 'email',
75
+ label: 'Email Address',
76
+ validation: { required: true, email: true },
77
+ },
78
+ ],
79
+ },
80
+ ];
81
+
82
+ // Action buttons configuration
83
+ const actionSchema = [
84
+ { text: 'Submit', action: 'submit' },
85
+ { text: 'Cancel', action: 'cancel' },
86
+ ];
87
+
88
+ // Form submission handler
89
+ const handleSubmit = (formState, action) => {
90
+ console.log('Form submitted:', formState.values);
91
+ console.log('Action:', action);
92
+ };
93
+
94
+ return (
95
+ <DynamicForm
96
+ questionGroups={questionGroups}
97
+ actionSchema={actionSchema}
98
+ onSubmit={handleSubmit}
99
+ />
100
+ );
101
+ };
102
+ ```
103
+
104
+ ### Real-World Example
105
+
106
+ Below is a comprehensive real-world implementation showcasing advanced features like API integration, state management, and theming:
107
+
108
+ ```jsx
109
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
110
+ import SectionListRenderer from '@ilife-tech/react-section-renderer';
111
+ import { DynamicForm } from '@ilife-tech/react-application-flow-renderer';
112
+
113
+ // Constants and configuration
114
+ const API_CONFIG = {
115
+ BASE_URL: process.env.REACT_APP_API_BASE_URL || '<API_BASE_URL>',
116
+ ENDPOINTS: {
117
+ STORE_VENDOR: '<API_ENDPOINT>',
118
+ },
119
+ MAX_API_ATTEMPTS: 8,
120
+ };
121
+
122
+ const INITIAL_QUESTIONS = [
123
+ {
124
+ name: 'userToken',
125
+ value: process.env.REACT_APP_USER_TOKEN || '<USER_TOKEN>',
126
+ },
127
+ { name: 'user', value: '<USER_TYPE>' },
128
+ { name: 'productName', value: '<PRODUCT_NAME>' },
129
+ { name: 'stateName', value: '<STATE_NAME>' },
130
+ ];
131
+
132
+ // API configuration for third-party integrations
133
+ const apiConfig = {
134
+ googlePlaces: {
135
+ apiKey: process.env.REACT_APP_GOOGLE_PLACES_API_KEY,
136
+ options: {
137
+ minSearchLength: 3,
138
+ debounceTime: 300,
139
+ componentRestrictions: {
140
+ country: process.env.REACT_APP_GOOGLE_PLACES_REGION || 'US',
141
+ },
142
+ types: ['address'],
143
+ fields: ['address_components', 'formatted_address', 'geometry', 'place_id'],
144
+ maxRequestsPerSecond: 10,
145
+ maxRequestsPerDay: 1000,
146
+ language: process.env.REACT_APP_GOOGLE_PLACES_LANGUAGE,
147
+ region: process.env.REACT_APP_GOOGLE_PLACES_REGION,
148
+ },
149
+ },
150
+ customApi: {
151
+ baseUrl: process.env.REACT_APP_CUSTOM_API_BASE_URL,
152
+ headers: {
153
+ Authorization: process.env.REACT_APP_AUTH_TOKEN,
154
+ 'Carrier-Auth': process.env.REACT_APP_CARRIER_AUTH,
155
+ },
156
+ },
157
+ };
158
+
159
+ // Theme configuration
160
+ const THEME = {
161
+ breakpoints: {
162
+ mobile: '@media (max-width: 480px)',
163
+ tablet: '@media (min-width: 481px) and (max-width: 768px)',
164
+ desktop: '@media (min-width: 769px)',
165
+ },
166
+ body: {
167
+ backgroundColor: '#FFFFFF',
168
+ color: '#333333',
169
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
170
+ fontSize: '16px',
171
+ lineHeight: '1.5',
172
+ },
173
+ form: {
174
+ backgroundColor: '#FFFFFF',
175
+ padding: '20px',
176
+ maxWidth: '800px',
177
+ margin: '0 auto',
178
+ fieldSpacing: '1.5rem',
179
+ groupSpacing: '2rem',
180
+ },
181
+ colors: {
182
+ primary: '#1D4ED8',
183
+ secondary: '#6B7280',
184
+ success: '#059669',
185
+ danger: '#DC2626',
186
+ warning: '#D97706',
187
+ info: '#2563EB',
188
+ light: '#F3F4F6',
189
+ dark: '#1F2937',
190
+ error: '#DC2626',
191
+ border: '#D1D5DB',
192
+ inputBackground: '#FFFFFF',
193
+ headerBackground: '#EFF6FF',
194
+ },
195
+ field: {
196
+ marginBottom: '1rem',
197
+ width: '100%',
198
+ },
199
+ label: {
200
+ display: 'block',
201
+ marginBottom: '0.5rem',
202
+ fontSize: '0.875rem',
203
+ fontWeight: '500',
204
+ color: '#374151',
205
+ },
206
+ input: {
207
+ width: '100%',
208
+ padding: '0.5rem',
209
+ fontSize: '1rem',
210
+ borderRadius: '0.375rem',
211
+ border: '1px solid #D1D5DB',
212
+ backgroundColor: '#FFFFFF',
213
+ '&:focus': {
214
+ outline: 'none',
215
+ borderColor: '#2563EB',
216
+ boxShadow: '0 0 0 2px rgba(37, 99, 235, 0.2)',
217
+ },
218
+ },
219
+ error: {
220
+ color: '#DC2626',
221
+ fontSize: '0.875rem',
222
+ marginTop: '0.25rem',
223
+ },
224
+ };
225
+
226
+ function App() {
227
+ // State Management
228
+ const [formState, setFormState] = useState({
229
+ storeVenderData: null,
230
+ questionGroups: [],
231
+ pageRules: [],
232
+ pageRuleGroup: [],
233
+ validationSchema: [],
234
+ actionSchema: [],
235
+ sectionSchema: [],
236
+ caseId: null,
237
+ pageId: null,
238
+ apiCallCounter: 0,
239
+ selectedAction: '',
240
+ error: null,
241
+ isLoading: false,
242
+ });
243
+
244
+ // Memoized API headers
245
+ const headers = useMemo(
246
+ () => ({
247
+ 'Content-Type': 'application/json',
248
+ Authorization: process.env.REACT_APP_AUTH_TOKEN || '<AUTH_TOKEN>',
249
+ CarrierAuthorization: process.env.REACT_APP_CARRIER_AUTH || '<CARRIER_AUTH_TOKEN>',
250
+ }),
251
+ []
252
+ );
253
+
254
+ // Transform form data with error handling
255
+ const transformFormData = useCallback((formData, schema) => {
256
+ try {
257
+ // Handle empty array or non-object formData
258
+ const formDataObj = Array.isArray(formData) || typeof formData !== 'object' ? {} : formData;
259
+ const { values = {}, visibility = {}, disabled = {} } = formDataObj;
260
+ const transformedData = [];
261
+
262
+ // Process form data based on schema
263
+ // ... (transform logic omitted for brevity)
264
+
265
+ return transformedData;
266
+ } catch (error) {
267
+ console.error('Error transforming form data:', error);
268
+ throw new Error('Failed to transform form data');
269
+ }
270
+ }, []);
271
+
272
+ // API call with error handling
273
+ const fetchStoreQuestionVendor = useCallback(
274
+ async (questions, action = '', nextPageId = '') => {
275
+ try {
276
+ const { caseId, pageId } = formState;
277
+ setFormState((prev) => ({ ...prev, isLoading: true, error: null }));
278
+
279
+ const requestPayload = {
280
+ caseId,
281
+ pageId,
282
+ questions,
283
+ user: '<USER_TYPE>',
284
+ agentId: '<AGENT_ID>',
285
+ templateName: '<PRODUCT_NAME>',
286
+ ...(action && { action }),
287
+ ...(nextPageId && { nextPageId }),
288
+ };
289
+
290
+ const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.STORE_VENDOR}`, {
291
+ method: 'POST',
292
+ headers,
293
+ body: JSON.stringify(requestPayload),
294
+ });
295
+
296
+ if (!response.ok) {
297
+ throw new Error(`API Error: ${response.status}`);
298
+ }
299
+
300
+ const { data: result } = await response.json();
301
+ const { pageDefinition, sectionList, caseId: newCaseId, pageId: newPageId } = result || {};
302
+
303
+ const {
304
+ questionGroups = [],
305
+ pageRules: rawPageRules = null,
306
+ pageRuleGroup: rawPageRuleGroup = null,
307
+ validationErrors = [],
308
+ actionButtons = [],
309
+ } = pageDefinition || {};
310
+
311
+ // Process and update state with API response
312
+ setFormState((prev) => ({
313
+ ...prev,
314
+ storeVenderData: result,
315
+ questionGroups,
316
+ pageRules: Array.isArray(rawPageRules) ? rawPageRules : [],
317
+ pageRuleGroup:
318
+ rawPageRuleGroup && typeof rawPageRuleGroup === 'object' ? rawPageRuleGroup : {},
319
+ validationSchema: validationErrors,
320
+ actionSchema: actionButtons,
321
+ sectionSchema: sectionList || [],
322
+ caseId: newCaseId || '',
323
+ pageId: newPageId || '',
324
+ selectedAction: action,
325
+ isLoading: false,
326
+ }));
327
+ } catch (error) {
328
+ console.error('API Error:', error);
329
+ setFormState((prev) => ({
330
+ ...prev,
331
+ error: error.message,
332
+ isLoading: false,
333
+ }));
334
+ }
335
+ },
336
+ [formState.caseId, formState.pageId, headers]
337
+ );
338
+
339
+ // Form submission handler
340
+ const handleSubmit = useCallback(
341
+ (newFormState, action) => {
342
+ const { questionGroups } = formState;
343
+ const transformedData = transformFormData(newFormState, questionGroups);
344
+ fetchStoreQuestionVendor(transformedData, action);
345
+ },
346
+ [fetchStoreQuestionVendor, formState.questionGroups, transformFormData]
347
+ );
348
+
349
+ // Section change handler
350
+ const handleSectionChange = useCallback(
351
+ (pageId) => {
352
+ const { questionGroups } = formState;
353
+ const transformedData = transformFormData([], questionGroups);
354
+ fetchStoreQuestionVendor(transformedData, '', pageId);
355
+ },
356
+ [fetchStoreQuestionVendor, formState.questionGroups, transformFormData]
357
+ );
358
+
359
+ // Initial data fetch
360
+ useEffect(() => {
361
+ fetchStoreQuestionVendor(INITIAL_QUESTIONS);
362
+ }, []);
363
+
364
+ // Extract commonly used state values
365
+ const {
366
+ error,
367
+ isLoading,
368
+ storeVenderData,
369
+ sectionSchema,
370
+ questionGroups,
371
+ pageRules,
372
+ pageRuleGroup,
373
+ validationSchema,
374
+ actionSchema,
375
+ pageId,
376
+ } = formState;
377
+
378
+ // Handle API triggers from form fields
379
+ const handleApiTrigger = (apiRequest) => {
380
+ // Implementation for API trigger handling
381
+ console.log('API request:', apiRequest);
382
+ // Return promise with response structure
383
+ return Promise.resolve({
384
+ success: true,
385
+ data: {
386
+ // Example: update options for a dropdown
387
+ options: [
388
+ { name: 'Option 1', value: 'option1' },
389
+ { name: 'Option 2', value: 'option2' },
390
+ ],
391
+ },
392
+ });
393
+ };
394
+
395
+ return (
396
+ <div className="app-container">
397
+ <header className="app-header">
398
+ <h1>Dynamic Form Example</h1>
399
+ </header>
400
+
401
+ <div className="app-main">
402
+ {error && (
403
+ <div className="error-message" role="alert">
404
+ {error}
405
+ </div>
406
+ )}
407
+
408
+ {isLoading && (
409
+ <div className="loading-indicator" role="status">
410
+ Loading...
411
+ </div>
412
+ )}
413
+
414
+ {storeVenderData && (
415
+ <>
416
+ <aside className="app-sidebar">
417
+ {!isLoading && sectionSchema?.length > 0 && (
418
+ <SectionListRenderer
419
+ sectionLists={sectionSchema}
420
+ onSectionClick={handleSectionChange}
421
+ customStyles={{ backgroundColor: '#f5f5f5' }}
422
+ pageId={pageId || sectionSchema[0]?.pageId}
423
+ />
424
+ )}
425
+ </aside>
426
+
427
+ <main className="app-content">
428
+ {questionGroups?.length > 0 && (
429
+ <DynamicForm
430
+ questionGroups={questionGroups}
431
+ pageRules={pageRules}
432
+ pageRuleGroup={pageRuleGroup}
433
+ validationErrors={validationSchema}
434
+ actionButtons={actionSchema}
435
+ theme={THEME}
436
+ onSubmit={handleSubmit}
437
+ onApiTrigger={handleApiTrigger}
438
+ apiConfig={apiConfig}
439
+ />
440
+ )}
441
+ </main>
442
+ </>
443
+ )}
444
+ </div>
445
+ </div>
446
+ );
447
+ }
448
+
449
+ export default App;
450
+ ```
451
+
452
+ ## Form Schema Structure
453
+
454
+ The form structure is defined by a JSON schema that specifies question groups and their fields. Each form consists of one or more question groups, each containing multiple field definitions.
455
+
456
+ ### Question Group Structure
457
+
458
+ ```javascript
459
+ {
460
+ groupId: string, // Unique identifier for the group
461
+ groupName: string, // Display name for the group (optional)
462
+ description: string, // Group description (optional)
463
+ columnCount: number, // Number of columns for layout (default: 1)
464
+ visible: boolean, // Group visibility (default: true)
465
+ questions: Array<Question> // Array of question objects
466
+ }
467
+ ```
468
+
469
+ ### Question (Field) Structure
470
+
471
+ ```javascript
472
+ {
473
+ questionId: string, // Unique identifier for the question/field
474
+ questionType: string, // Field type (text, dropdown, checkbox, etc.)
475
+ label: string, // Field label
476
+ placeholder: string, // Field placeholder (optional)
477
+ helpText: string, // Help text below the field (optional)
478
+ defaultValue: any, // Default field value (optional)
479
+ validation: Object, // Validation rules (optional)
480
+ visible: boolean, // Field visibility (default: true)
481
+ disabled: boolean, // Field disabled state (default: false)
482
+ required: boolean, // Whether field is required (default: false)
483
+ options: Array<Option>, // Options for select/dropdown/radio fields
484
+ layout: Object, // Layout configuration (optional)
485
+ style: Object, // Custom styling (optional)
486
+ apiTrigger: Object // API integration configuration (optional)
487
+ }
488
+ ```
489
+
490
+ ### Action Schema Structure
491
+
492
+ The action schema defines form buttons and their behaviors:
493
+
494
+ ```javascript
495
+ [
496
+ {
497
+ text: string, // Button text
498
+ action: string, // Action identifier (e.g., 'submit', 'back', 'next')
499
+ position: string, // Button position (optional, default: 'right')
500
+ },
501
+ ];
502
+ ```
503
+
504
+ ## API Integration
505
+
506
+ The package provides robust third-party API integration capabilities, allowing forms to fetch data, populate options, and submit data to external services.
507
+
508
+ ### Basic API Integration
509
+
510
+ API integration is handled through the `onApiTrigger` prop, which receives API trigger events from form fields:
511
+
512
+ ```jsx
513
+ import React from 'react';
514
+ import DynamicForm from '@ilife-tech/react-application-flow-renderer';
515
+ import { handleApiTrigger } from './services/apiService';
516
+
517
+ const MyForm = () => {
518
+ // Form schema with API trigger configuration
519
+ const questionGroups = [
520
+ {
521
+ groupId: 'locationInfo',
522
+ questions: [
523
+ {
524
+ questionId: 'country',
525
+ questionType: 'dropdown',
526
+ label: 'Country',
527
+ options: [
528
+ { name: 'United States', value: 'US' },
529
+ { name: 'Canada', value: 'CA' },
530
+ ],
531
+ },
532
+ {
533
+ questionId: 'state',
534
+ questionType: 'dropdown',
535
+ label: 'State/Province',
536
+ placeholder: 'Select a state',
537
+ // API trigger configuration
538
+ apiTrigger: {
539
+ type: 'dropdown',
540
+ dependsOn: ['country'], // Fields this API call depends on
541
+ apiUrl: 'getStatesByCountry', // API identifier
542
+ triggerEvents: ['onChange', 'onLoad'], // When to trigger API
543
+ },
544
+ },
545
+ ],
546
+ },
547
+ ];
548
+
549
+ // API configuration
550
+ const apiConfig = {
551
+ baseUrl: process.env.REACT_APP_API_BASE_URL || 'https://api.example.com',
552
+ headers: {
553
+ 'Content-Type': 'application/json',
554
+ Authorization: `Bearer ${process.env.REACT_APP_API_TOKEN}`,
555
+ },
556
+ timeout: 5000,
557
+ };
558
+
559
+ return (
560
+ <DynamicForm
561
+ questionGroups={questionGroups}
562
+ onApiTrigger={handleApiTrigger}
563
+ apiConfig={apiConfig}
564
+ />
565
+ );
566
+ };
567
+
568
+ export default MyForm;
569
+ ```
570
+
571
+ ### Implementing the API Handler
572
+
573
+ The API handler function processes API triggers and returns updates to the form. Here's a practical example based on the package's example implementation:
574
+
575
+ ```javascript
576
+ // services/apiService.js
577
+ import { createApiResponse } from '../utils/apiUtils';
578
+
579
+ /**
580
+ * Handle API trigger events from form fields
581
+ * @param {Object} triggerData - Data from the form's onApiTrigger event
582
+ * @returns {Promise<Object>} - Response with form updates
583
+ */
584
+ export const handleApiTrigger = async (triggerData) => {
585
+ const { question, value, triggerType, formState } = triggerData;
586
+
587
+ // Extract API configuration
588
+ const { apiTrigger, questionId } = question;
589
+
590
+ // Skip if no API trigger is configured
591
+ if (!apiTrigger || !apiTrigger.apiUrl) {
592
+ return null;
593
+ }
594
+
595
+ try {
596
+ // For dropdown fields that depend on other fields
597
+ if (question.questionType === 'dropdown' && triggerType === 'onChange') {
598
+ // Example: Fetch states based on selected country
599
+ if (apiTrigger.apiUrl === 'getStatesByCountry') {
600
+ const countryValue = formState.values[apiTrigger.dependsOn[0]];
601
+
602
+ // Don't proceed if no country is selected
603
+ if (!countryValue) {
604
+ return createApiResponse({
605
+ options: { [questionId]: [] },
606
+ values: { [questionId]: '' },
607
+ });
608
+ }
609
+
610
+ // Make API request
611
+ const response = await fetch(`${apiConfig.baseUrl}/states?country=${countryValue}`, {
612
+ headers: apiConfig.headers,
613
+ });
614
+
615
+ const data = await response.json();
616
+
617
+ // Map API response to dropdown options
618
+ const stateOptions = data.map((state) => ({
619
+ name: state.name,
620
+ value: state.code,
621
+ }));
622
+
623
+ // Return updates for the form
624
+ return createApiResponse({
625
+ options: { [questionId]: stateOptions },
626
+ visibility: { [questionId]: true },
627
+ disabled: { [questionId]: false },
628
+ });
629
+ }
630
+ }
631
+
632
+ return null;
633
+ } catch (error) {
634
+ console.error('API error:', error);
635
+
636
+ // Return error response
637
+ return createApiResponse({
638
+ apiErrors: { [questionId]: 'Failed to fetch data. Please try again.' },
639
+ });
640
+ }
641
+ };
642
+ ```
643
+
644
+ ### API Response Structure
645
+
646
+ API responses should be formatted as an object with field updates:
647
+
648
+ ```javascript
649
+ // utils/apiUtils.js
650
+
651
+ /**
652
+ * Create formatted API response for form updates
653
+ * @param {Object} updates - Updates to apply to the form
654
+ * @returns {Object} - Formatted response
655
+ */
656
+ export const createApiResponse = (updates = {}) => {
657
+ return {
658
+ // Field values to update
659
+ values: updates.values || {},
660
+
661
+ // Dropdown options to update
662
+ options: updates.options || {},
663
+
664
+ // Field visibility updates
665
+ visibility: updates.visibility || {},
666
+
667
+ // Field disabled state updates
668
+ disabled: updates.disabled || {},
669
+
670
+ // Field required state updates
671
+ required: updates.required || {},
672
+
673
+ // Error messages
674
+ apiErrors: updates.apiErrors || {},
675
+
676
+ // Loading states
677
+ apiLoading: updates.apiLoading || {},
678
+ };
679
+ };
680
+ ```
681
+
682
+ ### Advanced API Integration Example
683
+
684
+ Here's a more comprehensive example showing how to handle complex API integration with request queuing and response mapping:
685
+
686
+ ```javascript
687
+ // services/api/handlers/dropdownApiHandler.js
688
+
689
+ import { createApiResponse } from '../../../utils/apiUtils';
690
+
691
+ // Request queue for managing concurrent API calls
692
+ class ApiRequestQueue {
693
+ constructor() {
694
+ this.queue = new Map();
695
+ }
696
+
697
+ async enqueue(questionId, apiCall) {
698
+ if (this.queue.has(questionId)) {
699
+ return this.queue.get(questionId);
700
+ }
701
+
702
+ const promise = apiCall().finally(() => {
703
+ this.queue.delete(questionId);
704
+ });
705
+
706
+ this.queue.set(questionId, promise);
707
+ return promise;
708
+ }
709
+ }
710
+
711
+ const requestQueue = new ApiRequestQueue();
712
+
713
+ /**
714
+ * Handle API calls for dropdown fields
715
+ * @param {Object} triggerData - API trigger data
716
+ * @returns {Promise<Object>} Response with options and updates
717
+ */
718
+ export const handleDropdownApi = async (triggerData) => {
719
+ const { question, value, triggerType, formState } = triggerData;
720
+ const { questionId, apiTrigger } = question;
721
+
722
+ // Example: Handle different API endpoints based on apiUrl
723
+ switch (apiTrigger?.apiUrl) {
724
+ case 'getHouseholdMembers':
725
+ return requestQueue.enqueue(questionId, () =>
726
+ getHouseholdMembers(apiTrigger.apiUrl, formState)
727
+ );
728
+
729
+ case 'getStatesByCountry':
730
+ // Get dependent field value
731
+ const country = formState.values[apiTrigger.dependsOn[0]];
732
+ if (!country) {
733
+ return createApiResponse({
734
+ options: { [questionId]: [] },
735
+ disabled: { [questionId]: true },
736
+ });
737
+ }
738
+
739
+ return requestQueue.enqueue(questionId, () => getStatesByCountry(apiTrigger.apiUrl, country));
740
+
741
+ default:
742
+ return null;
743
+ }
744
+ };
745
+
746
+ /**
747
+ * Example API implementation for fetching states by country
748
+ */
749
+ async function getStatesByCountry(apiUrl, country) {
750
+ try {
751
+ // Here we'd normally implement our API call
752
+ const response = await fetch(`https://api.example.com/states?country=${country}`);
753
+ const data = await response.json();
754
+
755
+ // Map response to options format
756
+ const options = data.map((state) => ({
757
+ name: state.stateName || state.name,
758
+ value: state.stateCode || state.code,
759
+ }));
760
+
761
+ return createApiResponse({
762
+ options: { state: options },
763
+ disabled: { state: false },
764
+ visibility: { state: true },
765
+ });
766
+ } catch (error) {
767
+ console.error('API error:', error);
768
+ return createApiResponse({
769
+ apiErrors: { state: 'Failed to fetch states. Please try again.' },
770
+ options: { state: [] },
771
+ });
772
+ }
773
+ }
774
+ ```
775
+
776
+ ## Props Reference
777
+
778
+ The `DynamicForm` component accepts the following props:
779
+
780
+ | Prop | Type | Required | Default | Description |
781
+ | ------------------ | ---------- | -------- | ------- | ------------------------------------------------ |
782
+ | `questionGroups` | `Array` | Yes | `[]` | Array of question groups defining form structure |
783
+ | `actionSchema` | `Array` | No | `[]` | Array of action buttons configuration |
784
+ | `onSubmit` | `Function` | No | - | Form submission handler |
785
+ | `onApiTrigger` | `Function` | No | - | API trigger event handler |
786
+ | `apiConfig` | `Object` | No | `{}` | API configuration options |
787
+ | `theme` | `Object` | No | `{}` | Theme customization |
788
+ | `validationSchema` | `Array` | No | `[]` | Additional validation rules |
789
+ | `pageRules` | `Array` | No | `[]` | Page rules for conditional logic |
790
+ | `pageRuleGroup` | `Object` | No | `{}` | Page rule groups with nested conditions |
791
+ | `debug` | `Boolean` | No | `false` | Enable debug mode |
792
+ | `formApiRef` | `Object` | No | - | Ref object to access form API methods |
793
+ | `toastConfig` | `Object` | No | `{}` | Toast configuration |
794
+
795
+ ### Props Details
796
+
797
+ #### `questionGroups`
798
+
799
+ An array of question group objects that defines the form structure. Each group contains questions (fields) to render.
800
+
801
+ ```javascript
802
+ const questionGroups = [
803
+ {
804
+ groupId: 'group1',
805
+ groupName: 'Personal Information',
806
+ questions: [
807
+ /* question objects */
808
+ ],
809
+ },
810
+ ];
811
+ ```
812
+
813
+ #### `actionSchema`
814
+
815
+ An array of action button configurations for the form.
816
+
817
+ ```javascript
818
+ const actionSchema = [
819
+ { text: 'Submit', action: 'submit' },
820
+ { text: 'Previous', action: 'back' },
821
+ ];
822
+ ```
823
+
824
+ #### `onSubmit`
825
+
826
+ Callback function called when a form action is triggered. Receives the form state and action identifier.
827
+
828
+ ```javascript
829
+ const handleSubmit = (formState, action) => {
830
+ // formState contains values, errors, etc.
831
+ // action is the identifier of the action button clicked
832
+ if (action === 'submit') {
833
+ // Handle form submission
834
+ console.log('Form values:', formState.values);
835
+ }
836
+ };
837
+ ```
838
+
839
+ #### `onApiTrigger`
840
+
841
+ Callback function for handling API trigger events from form fields. Receives trigger data object.
842
+
843
+ ```javascript
844
+ const handleApiTrigger = async (triggerData) => {
845
+ // triggerData contains question, value, triggerType, and formState
846
+ // Return formatted API response with updates to the form
847
+ };
848
+ ```
849
+
850
+ #### `apiConfig`
851
+
852
+ Object containing API configuration options passed to the `onApiTrigger` handler.
853
+
854
+ ```javascript
855
+ const apiConfig = {
856
+ baseUrl: 'https://api.example.com',
857
+ headers: {
858
+ 'Content-Type': 'application/json',
859
+ Authorization: 'Bearer YOUR_TOKEN',
860
+ },
861
+ timeout: 5000,
862
+ };
863
+ ```
864
+
865
+ ## Field Types
866
+
867
+ The package supports a wide range of field types to handle various input requirements:
868
+
869
+ ### Basic Field Types
870
+
871
+ | Field Type | Component | Description |
872
+ | ---------- | --------------- | ------------------------------------- |
873
+ | `text` | `TextField` | Standard text input field |
874
+ | `textarea` | `TextAreaField` | Multi-line text input |
875
+ | `email` | `EmailField` | Email input with validation |
876
+ | `password` | `PasswordField` | Password input with visibility toggle |
877
+ | `number` | `NumberField` | Numeric input with constraints |
878
+ | `phone` | `PhoneField` | Phone number input with formatting |
879
+ | `currency` | `CurrencyField` | Monetary value input with formatting |
880
+ | `date` | `DateField` | Date picker with calendar |
881
+ | `datetime` | `DateTimeField` | Combined date and time picker |
882
+
883
+ ### Selection Field Types
884
+
885
+ | Field Type | Component | Description |
886
+ | ----------------- | ---------------------- | ------------------------------------------- |
887
+ | `dropdown` | `SelectField` | Dropdown selection menu |
888
+ | `combobox` | `ComboboxField` | Searchable dropdown with multiple selection |
889
+ | `radio` | `RadioField` | Single-selection radio buttons |
890
+ | `radioGroup` | `RadioGroupField` | Group of radio buttons with custom layout |
891
+ | `radioSwitch` | `RadioSwitchField` | Toggle-style radio selection |
892
+ | `radioTabButtons` | `RadioTabButtonsField` | Tab-style radio selection |
893
+ | `checkbox` | `CheckboxField` | Single checkbox |
894
+ | `checkboxGroup` | `CheckboxGroupField` | Group of checkboxes with multiple selection |
895
+
896
+ ### Specialized Field Types
897
+
898
+ | Field Type | Component | Description |
899
+ | ---------------- | --------------------- | ---------------------------------------------------------- |
900
+ | `address` | `AddressField` | Address input with validation and auto-complete |
901
+ | `beneficiary` | `BeneficiaryField` | Complex beneficiary information with percentage validation |
902
+ | `identification` | `IdentificationField` | SSN/ITIN input with masking and validation |
903
+ | `file` | `FileField` | File upload with preview |
904
+ | `pdf` | `PDFField` | PDF file upload and display |
905
+ | `cardList` | `CardListField` | Dynamic array of nested form fields |
906
+ | `display` | `DisplayTextField` | Read-only text display |
907
+ | `hyperlink` | `HyperlinkField` | Clickable hyperlink field |
908
+ | `button` | `ButtonField` | Action button field |
909
+ | `spinner` | `SpinnerField` | Loading indicator field |
910
+
911
+ ## Advanced Configuration
912
+
913
+ ### Toast Notifications
914
+
915
+ The Dynamic Form Renderer uses react-toastify for displaying notifications such as error messages, warnings, and success confirmations.
916
+
917
+ #### Toast Configuration
918
+
919
+ The form renderer integrates with your application's react-toastify setup. **Important: Your host application MUST include a `<ToastContainer />` component** for notifications to appear.
920
+
921
+ ```jsx
922
+ // Required: Import react-toastify in your host application
923
+ import { ToastContainer } from 'react-toastify';
924
+ import 'react-toastify/dist/ReactToastify.css';
925
+
926
+ const App = () => {
927
+ return (
928
+ <>
929
+ {/* Required: Add ToastContainer at the root level */}
930
+ <ToastContainer />
931
+
932
+ <DynamicForm
933
+ questionGroups={questionGroups}
934
+ toastConfig={{
935
+ disableToasts: false, // Disable toast notifications completely (default: false)
936
+ useHostToastify: true, // Whether to use host app's react-toastify (default: true)
937
+ }}
938
+ onSubmit={handleSubmit}
939
+ onApiTrigger={handleApiTrigger}
940
+ />
941
+ </>
942
+ );
943
+ };
944
+ ```
945
+
946
+ #### API Response Format for Toast Notifications
947
+
948
+ When implementing the `onApiTrigger` handler, you can return success and error messages that will be displayed as toast notifications:
949
+
950
+ ```javascript
951
+ // Example of API trigger handler with toast notifications
952
+ const handleApiTrigger = async (triggerData) => {
953
+ try {
954
+ // Your API logic here
955
+ const response = await myApiCall(triggerData);
956
+
957
+ // Return success message (will display as green toast)
958
+ return {
959
+ success: {
960
+ message: 'Operation completed successfully',
961
+ // You can also include field-specific success messages
962
+ // fieldName: 'Field updated successfully'
963
+ },
964
+ // Other response properties like updates, options, visibility...
965
+ };
966
+ } catch (error) {
967
+ // Return error message (will display as red toast)
968
+ return {
969
+ errors: {
970
+ error: 'An error occurred during the operation',
971
+ // You can also include field-specific error messages
972
+ // fieldName: 'Invalid value for this field'
973
+ },
974
+ };
975
+ }
976
+ };
977
+ ```
978
+
979
+ #### Integration with Host Applications
980
+
981
+ If your application already uses react-toastify, the renderer will:
982
+
983
+ 1. Use your application's react-toastify instance
984
+ 2. Respect your toast styling and configuration
985
+
986
+ **Important:** Only one `<ToastContainer />` should be rendered in your application tree. The package will not render its own ToastContainer - this must be provided by the host application.
987
+
988
+ ### Toast Configuration
989
+
990
+ The Dynamic Form Renderer uses react-toastify for displaying notifications. You can configure toast behavior using the `toastConfig` prop:
991
+
992
+ ```jsx
993
+ <DynamicForm
994
+ // Other props...
995
+ toastConfig={{
996
+ disableToasts: false, // Set to true to disable all toast notifications
997
+ useHostToastify: true, // Set to true to use host application's ToastContainer
998
+ // Additional toast options can be passed here
999
+ }}
1000
+ />
1001
+ ```
1002
+
1003
+ #### ToastContainer Requirements
1004
+
1005
+ To properly display toast notifications:
1006
+
1007
+ 1. Import ToastContainer and CSS in your host application:
1008
+
1009
+ ```jsx
1010
+ import { ToastContainer } from 'react-toastify';
1011
+ import 'react-toastify/dist/ReactToastify.css';
1012
+ ```
1013
+
1014
+ 2. Add the ToastContainer to your app's root component:
1015
+ ```jsx
1016
+ function App() {
1017
+ return (
1018
+ <>
1019
+ <YourAppComponents />
1020
+ <ToastContainer position="top-right" autoClose={5000} />
1021
+ </>
1022
+ );
1023
+ }
1024
+ ```
1025
+
1026
+ #### API Response Format for Toast Messages
1027
+
1028
+ When implementing custom API triggers with `onApiTrigger`, your handler should return a promise that resolves to an object with the following structure to properly display toast notifications:
1029
+
1030
+ ```javascript
1031
+ const handleApiTrigger = async (triggerData) => {
1032
+ // Your API call implementation
1033
+ try {
1034
+ // Process API call
1035
+ return {
1036
+ // Success messages that will trigger toast notifications
1037
+ success: {
1038
+ message: 'General success message', // General success toast
1039
+ fieldId: 'Field-specific success', // Field-specific success toast
1040
+ },
1041
+ // Optional field updates
1042
+ updates: {
1043
+ fieldId1: 'new value', // Update field values
1044
+ fieldId2: [{ name: 'Option 1', value: 'opt1' }], // Update field options
1045
+ },
1046
+ };
1047
+ } catch (error) {
1048
+ return {
1049
+ // Error messages that will trigger toast notifications
1050
+ errors: {
1051
+ error: 'General error message', // General error toast
1052
+ fieldId: 'Field-specific error', // Field-specific error toast
1053
+ },
1054
+ };
1055
+ }
1056
+ };
1057
+ ```
1058
+
1059
+ #### Compatibility Notes
1060
+
1061
+ - The Dynamic Form Renderer is compatible with react-toastify v9.1.x for React 17 applications
1062
+ - For React 18+ applications, you can use react-toastify v9.x or higher
1063
+ - Toast styles will follow your application's theme if available
1064
+ - Error and success messages from API responses and form validation will use appropriate toast types
1065
+
1066
+ #### Troubleshooting
1067
+
1068
+ - **No toasts appear**: Ensure your application has a `<ToastContainer />` component and has imported the CSS
1069
+ - **\_useSyncExternalStore error**: This usually indicates a version compatibility issue between React and react-toastify. For React 17, use react-toastify v9.1.x
1070
+ - **Multiple toasts appear**: Ensure you only have one `<ToastContainer />` in your application
1071
+
1072
+ ### Theme Customization
1073
+
1074
+ The DynamicForm component supports deep theme customization through the `theme` prop:
1075
+
1076
+ ```javascript
1077
+ const customTheme = {
1078
+ typography: {
1079
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
1080
+ fontSize: '16px',
1081
+ lineHeight: 1.5,
1082
+ headings: {
1083
+ h1: { fontSize: '2rem', fontWeight: 700 },
1084
+ h2: { fontSize: '1.5rem', fontWeight: 600 },
1085
+ h3: { fontSize: '1.25rem', fontWeight: 600 },
1086
+ },
1087
+ },
1088
+ palette: {
1089
+ primary: {
1090
+ main: '#0066cc',
1091
+ light: '#3383d6',
1092
+ dark: '#004c99',
1093
+ contrastText: '#ffffff',
1094
+ },
1095
+ error: {
1096
+ main: '#d32f2f',
1097
+ light: '#ef5350',
1098
+ dark: '#c62828',
1099
+ contrastText: '#ffffff',
1100
+ },
1101
+ success: {
1102
+ main: '#2e7d32',
1103
+ light: '#4caf50',
1104
+ dark: '#1b5e20',
1105
+ contrastText: '#ffffff',
1106
+ },
1107
+ },
1108
+ spacing: {
1109
+ unit: '8px',
1110
+ form: '24px',
1111
+ field: '16px',
1112
+ },
1113
+ shape: {
1114
+ borderRadius: '4px',
1115
+ },
1116
+ breakpoints: {
1117
+ xs: 0,
1118
+ sm: 600,
1119
+ md: 960,
1120
+ lg: 1280,
1121
+ xl: 1920,
1122
+ },
1123
+ fields: {
1124
+ input: {
1125
+ height: '40px',
1126
+ padding: '8px 12px',
1127
+ fontSize: '16px',
1128
+ borderRadius: '4px',
1129
+ borderColor: '#cccccc',
1130
+ focusBorderColor: '#0066cc',
1131
+ errorBorderColor: '#d32f2f',
1132
+ background: '#ffffff',
1133
+ },
1134
+ label: {
1135
+ fontSize: '14px',
1136
+ fontWeight: 600,
1137
+ marginBottom: '4px',
1138
+ color: '#333333',
1139
+ },
1140
+ helperText: {
1141
+ fontSize: '12px',
1142
+ color: '#666666',
1143
+ marginTop: '4px',
1144
+ },
1145
+ error: {
1146
+ fontSize: '12px',
1147
+ color: '#d32f2f',
1148
+ marginTop: '4px',
1149
+ },
1150
+ },
1151
+ buttons: {
1152
+ primary: {
1153
+ background: '#0066cc',
1154
+ color: '#ffffff',
1155
+ hoverBackground: '#004c99',
1156
+ fontSize: '16px',
1157
+ padding: '8px 24px',
1158
+ borderRadius: '4px',
1159
+ },
1160
+ secondary: {
1161
+ background: '#f5f5f5',
1162
+ color: '#333333',
1163
+ hoverBackground: '#e0e0e0',
1164
+ fontSize: '16px',
1165
+ padding: '8px 24px',
1166
+ borderRadius: '4px',
1167
+ },
1168
+ },
1169
+ };
1170
+
1171
+ // Usage
1172
+ <DynamicForm
1173
+ questionGroups={questionGroups}
1174
+ theme={customTheme}
1175
+ // Other props
1176
+ />;
1177
+ ```
1178
+
1179
+ ### Validation Configuration
1180
+
1181
+ #### Client-side Validation
1182
+
1183
+ Validation can be defined directly in the form schema or through a separate validation schema:
1184
+
1185
+ ```javascript
1186
+ // In form schema
1187
+ const questionGroups = [
1188
+ {
1189
+ groupId: 'personalInfo',
1190
+ questions: [
1191
+ {
1192
+ questionId: 'email',
1193
+ questionType: 'email',
1194
+ label: 'Email Address',
1195
+ validation: {
1196
+ required: true,
1197
+ email: true,
1198
+ requiredMessage: 'Email is required',
1199
+ errorMessage: 'Please enter a valid email address',
1200
+ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
1201
+ patternMessage: 'Email format is invalid',
1202
+ },
1203
+ },
1204
+ {
1205
+ questionId: 'password',
1206
+ questionType: 'password',
1207
+ label: 'Password',
1208
+ validation: {
1209
+ required: true,
1210
+ minLength: 8,
1211
+ minLengthMessage: 'Password must be at least 8 characters',
1212
+ pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
1213
+ patternMessage:
1214
+ 'Password must include uppercase, lowercase, number and special character',
1215
+ },
1216
+ },
1217
+ ],
1218
+ },
1219
+ ];
1220
+
1221
+ // Separate validation schema
1222
+ const validationSchema = [
1223
+ {
1224
+ field: 'confirmPassword',
1225
+ rules: [
1226
+ 'required',
1227
+ {
1228
+ name: 'matchesField',
1229
+ params: ['password'],
1230
+ message: 'Passwords must match',
1231
+ },
1232
+ ],
1233
+ },
1234
+ ];
1235
+ ```
1236
+
1237
+ #### Available Validation Message Types
1238
+
1239
+ | Message Type | Description |
1240
+ | ------------------ | --------------------------------------------------- |
1241
+ | `requiredMessage` | Displayed when a required field is empty |
1242
+ | `errorMessage` | General error message for field validation |
1243
+ | `lengthMessage` | Error for overall length validation |
1244
+ | `minLengthMessage` | Error for minimum length validation |
1245
+ | `maxLengthMessage` | Error for maximum length validation |
1246
+ | `patternMessage` | Error for regex pattern validation |
1247
+ | `typeMessage` | Error for type validation (email, number, etc.) |
1248
+ | `formatMessage` | Error for format validation (phone, currency, etc.) |
1249
+
1250
+ ### CardList Configuration
1251
+
1252
+ The `CardListField` component enables dynamic form arrays with complete validation and state management:
1253
+
1254
+ ```javascript
1255
+ const cardListQuestion = {
1256
+ questionId: 'beneficiaries',
1257
+ questionType: 'cardList',
1258
+ label: 'Beneficiaries',
1259
+ addCardLabel: 'Add Beneficiary',
1260
+ cardSchema: [
1261
+ {
1262
+ groupId: 'beneficiaryInfo',
1263
+ questions: [
1264
+ {
1265
+ questionId: 'name',
1266
+ questionType: 'text',
1267
+ label: 'Full Name',
1268
+ validation: { required: true },
1269
+ },
1270
+ {
1271
+ questionId: 'relationship',
1272
+ questionType: 'dropdown',
1273
+ label: 'Relationship',
1274
+ options: [
1275
+ { name: 'Spouse', value: 'spouse' },
1276
+ { name: 'Child', value: 'child' },
1277
+ { name: 'Parent', value: 'parent' },
1278
+ { name: 'Other', value: 'other' },
1279
+ ],
1280
+ validation: { required: true },
1281
+ },
1282
+ {
1283
+ questionId: 'percentage',
1284
+ questionType: 'currency',
1285
+ label: 'Percentage',
1286
+ validation: {
1287
+ required: true,
1288
+ min: 0,
1289
+ max: 100,
1290
+ minMessage: 'Percentage must be positive',
1291
+ maxMessage: 'Percentage cannot exceed 100%',
1292
+ },
1293
+ },
1294
+ ],
1295
+ },
1296
+ ],
1297
+ validation: {
1298
+ minCards: 1,
1299
+ maxCards: 5,
1300
+ minCardsMessage: 'At least one beneficiary is required',
1301
+ maxCardsMessage: 'Maximum of 5 beneficiaries allowed',
1302
+ totalValidator: (cards) => {
1303
+ // Calculate total percentage across all cards
1304
+ const total = cards.reduce((sum, card) => {
1305
+ const percentage = parseFloat(card.values.percentage) || 0;
1306
+ return sum + percentage;
1307
+ }, 0);
1308
+
1309
+ // Validate total is 100%
1310
+ if (Math.abs(total - 100) > 0.01) {
1311
+ return 'Total percentage must equal 100%';
1312
+ }
1313
+
1314
+ return null; // No error
1315
+ },
1316
+ },
1317
+ };
1318
+ ```
1319
+
1320
+ #### CardList State Management
1321
+
1322
+ Each card in a CardList manages its own state, including:
1323
+
1324
+ - Form values
1325
+ - Validation errors
1326
+ - Visibility rules
1327
+ - Disabled states
1328
+
1329
+ You can access the CardList state through the form context:
1330
+
1331
+ ```javascript
1332
+ const MyForm = () => {
1333
+ const formApiRef = useRef(null);
1334
+
1335
+ const handleSubmit = (formState) => {
1336
+ // Access cardList data as an array of card states
1337
+ const beneficiaries = formState.values.beneficiaries;
1338
+ console.log('Beneficiary cards:', beneficiaries);
1339
+ };
1340
+
1341
+ return (
1342
+ <DynamicForm questionGroups={questionGroups} onSubmit={handleSubmit} formApiRef={formApiRef} />
1343
+ );
1344
+ };
1345
+ ```
1346
+
1347
+ ## Troubleshooting
1348
+
1349
+ ### Common Issues
1350
+
1351
+ #### API Integration Issues
1352
+
1353
+ | Issue | Solution |
1354
+ | ------------------------------- | ---------------------------------------------------------------------------------- |
1355
+ | API calls not triggering | Ensure `apiTrigger` configuration in field schema includes correct `triggerEvents` |
1356
+ | API responses not updating form | Verify that the `onApiTrigger` handler returns the correct response structure |
1357
+ | Multiple concurrent API calls | Use request queuing as shown in the advanced API integration example |
1358
+ | CORS errors | Configure proper CORS headers in your API or use a proxy service |
1359
+
1360
+ #### Validation Issues
1361
+
1362
+ | Issue | Solution |
1363
+ | ------------------------------ | -------------------------------------------------------------------------------- |
1364
+ | ValidationSchema not applying | Ensure field names in validation schema match questionIds in form schema |
1365
+ | Custom validation not working | Check that validation functions return error message strings, not boolean values |
1366
+ | Form submitting with errors | Verify that onSubmit handler checks formState.errors before proceeding |
1367
+ | Cross-field validation failing | Use the validationSchema approach for dependencies between fields |
1368
+
1369
+ #### Rendering Issues
1370
+
1371
+ | Issue | Solution |
1372
+ | ------------------------------------ | -------------------------------------------------------------- |
1373
+ | Fields not respecting column layout | Check columnCount setting in group configuration |
1374
+ | Theme customizations not applying | Ensure theme prop structure matches expected format |
1375
+ | Responsive layout issues | Verify breakpoints in theme configuration |
1376
+ | Fields rendering in unexpected order | Check if columns are properly balanced in group configurations |
1377
+
1378
+ ### Debugging Tips
1379
+
1380
+ #### Using Debug Mode
1381
+
1382
+ Enable debug mode to get verbose console output of form state changes:
1383
+
1384
+ ```jsx
1385
+ <DynamicForm
1386
+ questionGroups={questionGroups}
1387
+ debug={true}
1388
+ // Other props
1389
+ />
1390
+ ```
1391
+
1392
+ #### Accessing Form API
1393
+
1394
+ Use the formApiRef prop to access form methods imperatively:
1395
+
1396
+ ```jsx
1397
+ const MyForm = () => {
1398
+ const formApiRef = useRef(null);
1399
+
1400
+ const handleClick = () => {
1401
+ // Access form API methods
1402
+ const api = formApiRef.current;
1403
+
1404
+ // Get current form state
1405
+ const state = api.getState();
1406
+ console.log('Form values:', state.values);
1407
+
1408
+ // Set field value
1409
+ api.setValue('email', 'user@example.com');
1410
+
1411
+ // Validate specific field
1412
+ api.validateField('email');
1413
+
1414
+ // Validate entire form
1415
+ api.validateForm().then((errors) => {
1416
+ console.log('Validation errors:', errors);
1417
+ });
1418
+ };
1419
+
1420
+ return (
1421
+ <>
1422
+ <DynamicForm questionGroups={questionGroups} formApiRef={formApiRef} />
1423
+ <button onClick={handleClick}>Debug Form</button>
1424
+ </>
1425
+ );
1426
+ };
1427
+ ```
1428
+
1429
+ ### Performance Optimization
1430
+
1431
+ #### Large Forms
1432
+
1433
+ For large forms with many fields:
1434
+
1435
+ - Split form into multiple pages or sections
1436
+ - Use the `visibleGroups` prop to only render visible question groups
1437
+ - Implement form sections with conditional rendering
1438
+ - Consider lazy loading for complex field components
1439
+
1440
+ #### API Efficiency
1441
+
1442
+ Improve API integration performance:
1443
+
1444
+ - Implement response caching as shown in API integration examples
1445
+ - Use debounce for onChange API triggers
1446
+ - Batch API calls when possible
1447
+ - Implement request cancellation for outdated requests
1448
+
1449
+ ## Examples & Best Practices
1450
+
1451
+ ### Real-world Implementation Patterns
1452
+
1453
+ #### Multi-step Form Wizard
1454
+
1455
+ Implement a form wizard with progress tracking:
1456
+
1457
+ ```jsx
1458
+ import React, { useState } from 'react';
1459
+ import DynamicForm from '@ilife-tech/react-application-flow-renderer';
1460
+
1461
+ const MultiStepForm = () => {
1462
+ const [step, setStep] = useState(0);
1463
+ const [formData, setFormData] = useState({});
1464
+
1465
+ // Define steps with their schemas
1466
+ const steps = [
1467
+ {
1468
+ title: 'Personal Information',
1469
+ schema: [
1470
+ {
1471
+ groupId: 'personal',
1472
+ questions: [
1473
+ /* Personal info questions */
1474
+ ],
1475
+ },
1476
+ ],
1477
+ actions: [{ text: 'Next', action: 'next' }],
1478
+ },
1479
+ {
1480
+ title: 'Contact Details',
1481
+ schema: [
1482
+ {
1483
+ groupId: 'contact',
1484
+ questions: [
1485
+ /* Contact info questions */
1486
+ ],
1487
+ },
1488
+ ],
1489
+ actions: [
1490
+ { text: 'Back', action: 'back' },
1491
+ { text: 'Next', action: 'next' },
1492
+ ],
1493
+ },
1494
+ {
1495
+ title: 'Review & Submit',
1496
+ schema: [
1497
+ {
1498
+ groupId: 'review',
1499
+ questions: [
1500
+ /* Review fields */
1501
+ ],
1502
+ },
1503
+ ],
1504
+ actions: [
1505
+ { text: 'Back', action: 'back' },
1506
+ { text: 'Submit', action: 'submit' },
1507
+ ],
1508
+ },
1509
+ ];
1510
+
1511
+ const currentStep = steps[step];
1512
+
1513
+ const handleAction = (state, action) => {
1514
+ if (action === 'next' && step < steps.length - 1) {
1515
+ // Store current step data
1516
+ setFormData((prev) => ({ ...prev, ...state.values }));
1517
+ setStep(step + 1);
1518
+ } else if (action === 'back' && step > 0) {
1519
+ setStep(step - 1);
1520
+ } else if (action === 'submit') {
1521
+ // Combine all step data and submit
1522
+ const finalData = { ...formData, ...state.values };
1523
+ console.log('Submitting form:', finalData);
1524
+ // Submit to server
1525
+ }
1526
+ };
1527
+
1528
+ return (
1529
+ <div className="multi-step-form">
1530
+ <div className="progress-bar">
1531
+ {steps.map((s, i) => (
1532
+ <div key={i} className={`step ${i <= step ? 'active' : ''}`}>
1533
+ {s.title}
1534
+ </div>
1535
+ ))}
1536
+ </div>
1537
+
1538
+ <DynamicForm
1539
+ questionGroups={currentStep.schema}
1540
+ actionSchema={currentStep.actions}
1541
+ initialValues={formData}
1542
+ onSubmit={handleAction}
1543
+ />
1544
+ </div>
1545
+ );
1546
+ };
1547
+ ```
1548
+
1549
+ #### Dynamic API-driven Form
1550
+
1551
+ Create a form that dynamically loads its schema from an API:
1552
+
1553
+ ```jsx
1554
+ import React, { useState, useEffect } from 'react';
1555
+ import DynamicForm from '@ilife-tech/react-application-flow-renderer';
1556
+ import { handleApiTrigger } from './services/apiService';
1557
+
1558
+ const DynamicApiForm = ({ formId }) => {
1559
+ const [loading, setLoading] = useState(true);
1560
+ const [error, setError] = useState(null);
1561
+ const [formSchema, setFormSchema] = useState(null);
1562
+
1563
+ useEffect(() => {
1564
+ async function fetchFormSchema() {
1565
+ try {
1566
+ setLoading(true);
1567
+
1568
+ // Fetch form schema from API
1569
+ const response = await fetch(`https://api.example.com/forms/${formId}`);
1570
+
1571
+ if (!response.ok) {
1572
+ throw new Error(`Failed to load form: ${response.statusText}`);
1573
+ }
1574
+
1575
+ const data = await response.json();
1576
+
1577
+ setFormSchema({
1578
+ questionGroups: data.groups,
1579
+ actionSchema: data.actions,
1580
+ initialValues: data.initialValues || {},
1581
+ });
1582
+ } catch (err) {
1583
+ setError(err.message);
1584
+ } finally {
1585
+ setLoading(false);
1586
+ }
1587
+ }
1588
+
1589
+ fetchFormSchema();
1590
+ }, [formId]);
1591
+
1592
+ const handleSubmit = async (formState, action) => {
1593
+ if (action === 'submit') {
1594
+ try {
1595
+ // Submit form data to API
1596
+ const response = await fetch('https://api.example.com/submissions', {
1597
+ method: 'POST',
1598
+ headers: {
1599
+ 'Content-Type': 'application/json',
1600
+ },
1601
+ body: JSON.stringify({
1602
+ formId,
1603
+ values: formState.values,
1604
+ }),
1605
+ });
1606
+
1607
+ const result = await response.json();
1608
+ console.log('Submission result:', result);
1609
+ } catch (err) {
1610
+ console.error('Submission error:', err);
1611
+ }
1612
+ }
1613
+ };
1614
+
1615
+ if (loading) {
1616
+ return <div className="loading">Loading form...</div>;
1617
+ }
1618
+
1619
+ if (error) {
1620
+ return <div className="error">{error}</div>;
1621
+ }
1622
+
1623
+ return (
1624
+ <DynamicForm
1625
+ questionGroups={formSchema.questionGroups}
1626
+ actionSchema={formSchema.actionSchema}
1627
+ initialValues={formSchema.initialValues}
1628
+ onSubmit={handleSubmit}
1629
+ onApiTrigger={handleApiTrigger}
1630
+ />
1631
+ );
1632
+ };
1633
+ ```
1634
+
1635
+ ### Security Best Practices
1636
+
1637
+ 1. **API Key Management**:
1638
+
1639
+ - Never hardcode API keys in your JavaScript code
1640
+ - Use environment variables for all sensitive credentials
1641
+ - Consider using token exchange services for third-party APIs
1642
+
1643
+ 2. **Sensitive Data Handling**:
1644
+
1645
+ - Use the built-in masking features for fields like SSN and credit card numbers
1646
+ - Avoid storing sensitive data in localStorage or sessionStorage
1647
+ - Clear sensitive form data after submission
1648
+
1649
+ 3. **Input Validation**:
1650
+ - Always validate both on client and server side
1651
+ - Use strong validation rules for sensitive fields
1652
+ - Implement proper error handling for failed validations
1653
+
1654
+ ### Accessibility Guidelines
1655
+
1656
+ 1. **Screen Reader Support**:
1657
+
1658
+ - All form fields include proper ARIA attributes
1659
+ - Error messages are announced to screen readers
1660
+ - Focus management follows a logical flow
1661
+
1662
+ 2. **Keyboard Navigation**:
1663
+
1664
+ - All interactive elements are focusable
1665
+ - Tab order follows a logical sequence
1666
+ - Custom components support keyboard interactions
1667
+
1668
+ 3. **Visual Considerations**:
1669
+ - Color is not the only means of conveying information
1670
+ - Sufficient color contrast for text and UI elements
1671
+ - Form supports browser zoom and text resizing
1672
+
1673
+ ## Conclusion
1674
+
1675
+ The React Dynamic Form Renderer package provides a comprehensive solution for creating dynamic, interactive forms with advanced validation, conditional logic, and third-party API integration. By leveraging the JSON schema approach, you can rapidly develop complex forms without sacrificing flexibility or control.
1676
+
1677
+ For more detailed documentation, refer to the [user guide](./docs/user-guide.md) or check out the [example implementations](./example) included with the package.
1678
+
1679
+ We welcome contributions and feedback! Please file issues or submit pull requests on our [GitHub repository](https://github.com/ilife-technologies/react-dynamic-form-renderer).