@ilife-tech/react-application-flow-renderer 1.3.44 → 1.3.45

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