@ilife-tech/react-application-flow-renderer 1.0.68 → 1.0.70

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