react-form-dto 1.0.4 → 1.0.6
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 +198 -310
- package/dist/components/FormBuilder.d.ts +17 -11
- package/dist/example/Demo.d.ts +1 -0
- package/dist/form/FormContext.d.ts +41 -0
- package/dist/form/FormProvider.d.ts +8 -0
- package/dist/form/useFieldArray.d.ts +10 -0
- package/dist/form/useForm.d.ts +10 -0
- package/dist/form/useWatch.d.ts +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useFormDTO.d.ts +21 -0
- package/dist/index.d.ts +8 -1
- package/dist/react-form-dto.es.js +570 -363
- package/dist/react-form-dto.umd.js +2 -2
- package/dist/stories/Demo.stories.d.ts +16 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -18,14 +18,14 @@ It is designed for **schema-driven UIs**, backend-configured workflows, admin pa
|
|
|
18
18
|
Most form libraries solve **state management**.
|
|
19
19
|
React Form DTO solves **form architecture**.
|
|
20
20
|
|
|
21
|
-
It operates at a higher abstraction level where **layout, validation, rendering, and behavior** are defined in a single schema.
|
|
21
|
+
It operates at a higher abstraction level where **layout, validation, rendering, and behavior** are defined in a single schema — with a react-hook-form–style hook API to manage state and submission.
|
|
22
22
|
|
|
23
23
|
### Use this library when:
|
|
24
24
|
|
|
25
25
|
- Forms are generated from backend schemas or configuration APIs
|
|
26
26
|
- UI logic must be reused across multiple applications
|
|
27
27
|
- Forms are large, dynamic, or conditional
|
|
28
|
-
- You need
|
|
28
|
+
- You need reactive watches, field arrays, or context-driven child components
|
|
29
29
|
- Your design system is based on Material UI
|
|
30
30
|
|
|
31
31
|
### Key Advantages
|
|
@@ -33,7 +33,7 @@ It operates at a higher abstraction level where **layout, validation, rendering,
|
|
|
33
33
|
- 📄 **DTO-first design** – define forms entirely in JSON or TypeScript
|
|
34
34
|
- 🎨 **Material UI v7 native** – accessibility and consistency by default
|
|
35
35
|
- 🧱 **Composable structure** – Form → Section → Field
|
|
36
|
-
-
|
|
36
|
+
- 🪝 **Hook-based API** – `useFormDTO`, `useForm`, `useWatch`, `useFieldArray`, `useFormContext`
|
|
37
37
|
- 🔀 **Conditional rendering** – dynamic visibility and logic
|
|
38
38
|
- 🧩 **Extensible renderers** – plug in custom components
|
|
39
39
|
- 🛡️ **Strong TypeScript typing** – safe, predictable APIs
|
|
@@ -48,12 +48,12 @@ It operates at a higher abstraction level where **layout, validation, rendering,
|
|
|
48
48
|
|------|---------------|----------------|--------|
|
|
49
49
|
| Schema / DTO driven | ✅ Native | ❌ Manual | ❌ Manual |
|
|
50
50
|
| MUI-first | ✅ Yes | ⚠️ Partial | ⚠️ Partial |
|
|
51
|
-
|
|
|
51
|
+
| Hook-based API | ✅ First-class | ✅ Yes | ⚠️ Partial |
|
|
52
52
|
| Large dynamic forms | ✅ Excellent | ⚠️ Medium | ❌ Poor |
|
|
53
53
|
| Boilerplate | ✅ Minimal | ❌ High | ❌ High |
|
|
54
54
|
|
|
55
55
|
> **Note:** React Form DTO is **not a replacement** for React Hook Form.
|
|
56
|
-
> It is a **higher-level abstraction** for schema-driven UI generation.
|
|
56
|
+
> It is a **higher-level abstraction** for schema-driven UI generation, with a familiar hook API on top.
|
|
57
57
|
|
|
58
58
|
---
|
|
59
59
|
|
|
@@ -103,188 +103,216 @@ See [CHANGELOG.md](./.github/CHANGELOG.md) for detailed version history.
|
|
|
103
103
|
|
|
104
104
|
### DTO as Source of Truth
|
|
105
105
|
|
|
106
|
-
All structure, layout, validation, and behavior live in a single schema.
|
|
106
|
+
All structure, layout, validation, and behavior live in a single schema object. Define it once — the library handles the rest.
|
|
107
107
|
|
|
108
|
-
###
|
|
108
|
+
### Hook-based State Management
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
`useFormDTO` bridges the schema into a react-hook-form–style context. Any component inside `<FormProvider>` can read values, watch fields, and access errors without prop-drilling.
|
|
111
111
|
|
|
112
|
-
###
|
|
112
|
+
### Renderer Isolation
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
Field logic is decoupled from presentation, enabling full customization via the `renderers` prop.
|
|
115
115
|
|
|
116
|
-
###
|
|
116
|
+
### Imperative Escape Hatch
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
A ref-based API (`FormBuilderHandle`) is available for scenarios that need programmatic control outside the React tree.
|
|
119
|
+
|
|
120
|
+
---
|
|
119
121
|
|
|
120
122
|
## Quick Start
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
import {
|
|
126
|
-
|
|
127
|
-
const formRef = useRef<FormBuilderHandle>(null);
|
|
128
|
-
|
|
129
|
-
const handleSubmit = (e) => {
|
|
130
|
-
e.preventDefault();
|
|
131
|
-
if (!formRef.current) return;
|
|
132
|
-
const values = formRef.current.getValues();
|
|
133
|
-
const errors = formRef.current.validateAll();
|
|
134
|
-
formRef.current.handleChange?.("firstName", "NewName");
|
|
135
|
-
const firstNameErrors = formRef.current.validateField("firstName");
|
|
136
|
-
const allErrors = formRef.current.getErrors();
|
|
137
|
-
};
|
|
124
|
+
### 1 — Define a FormDTO
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import type { FormDTO } from 'react-form-dto';
|
|
138
128
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
129
|
+
const profileForm: FormDTO = {
|
|
130
|
+
title: "User Profile",
|
|
131
|
+
sections: [
|
|
132
|
+
{
|
|
133
|
+
id: "personal",
|
|
134
|
+
heading: "Personal Information",
|
|
135
|
+
fields: [
|
|
136
|
+
{
|
|
137
|
+
id: "firstName",
|
|
138
|
+
type: "text",
|
|
139
|
+
label: "First Name",
|
|
140
|
+
layout: { cols: 6 },
|
|
141
|
+
validations: { required: "First name is required" },
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "lastName",
|
|
145
|
+
type: "text",
|
|
146
|
+
label: "Last Name",
|
|
147
|
+
layout: { cols: 6 },
|
|
148
|
+
validations: { required: "Last name is required" },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "email",
|
|
152
|
+
type: "email",
|
|
153
|
+
label: "Email",
|
|
154
|
+
validations: {
|
|
155
|
+
required: "Email is required",
|
|
156
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
145
163
|
```
|
|
146
164
|
|
|
147
|
-
|
|
165
|
+
### 2 — Render with FormProvider
|
|
148
166
|
|
|
149
|
-
|
|
167
|
+
Pass the DTO to `useFormDTO`, wrap in `<FormProvider>`, and render with `<FormBuilder>`. `form.handleSubmit` validates all fields before calling your callback.
|
|
150
168
|
|
|
151
|
-
|
|
169
|
+
```tsx
|
|
170
|
+
import { FormBuilder, FormProvider, useFormDTO } from 'react-form-dto';
|
|
171
|
+
import { profileForm } from './profileForm';
|
|
152
172
|
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
export function ProfilePage() {
|
|
174
|
+
const form = useFormDTO(profileForm);
|
|
155
175
|
|
|
156
|
-
|
|
176
|
+
const onSubmit = (data: Record<string, any>) => {
|
|
177
|
+
console.log('Submitted:', data);
|
|
178
|
+
};
|
|
157
179
|
|
|
158
|
-
|
|
180
|
+
return (
|
|
181
|
+
<FormProvider value={form}>
|
|
182
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
183
|
+
<FormBuilder dto={profileForm} />
|
|
184
|
+
<button type="submit">Submit</button>
|
|
185
|
+
</form>
|
|
186
|
+
</FormProvider>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`useFormDTO` automatically extracts default values and wires all `validations` rules from the DTO into the form context. Errors appear after a field is touched — not on initial load.
|
|
159
192
|
|
|
160
|
-
|
|
161
|
-
- You need to:
|
|
162
|
-
- Run validation on submit
|
|
163
|
-
- Read values/errors from outside the form
|
|
164
|
-
- Prefill fields programmatically
|
|
165
|
-
- Plug in custom field renderers
|
|
193
|
+
### 3 — Read state from child components
|
|
166
194
|
|
|
167
|
-
|
|
195
|
+
Any component inside `<FormProvider>` can subscribe to form state:
|
|
168
196
|
|
|
169
197
|
```tsx
|
|
170
|
-
import {
|
|
171
|
-
import { myFormDTO } from './myFormDTO';
|
|
198
|
+
import { useWatch, useFormContext } from 'react-form-dto';
|
|
172
199
|
|
|
173
|
-
function
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
});
|
|
200
|
+
function LiveGreeting() {
|
|
201
|
+
const firstName = useWatch('firstName');
|
|
202
|
+
return <p>Hello, {firstName || 'stranger'}!</p>;
|
|
203
|
+
}
|
|
178
204
|
|
|
179
|
-
|
|
180
|
-
|
|
205
|
+
function SubmitButton() {
|
|
206
|
+
const { errors } = useFormContext();
|
|
207
|
+
const hasErrors = Object.values(errors).some(Boolean);
|
|
208
|
+
return <button type="submit" disabled={hasErrors}>Submit</button>;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
181
213
|
|
|
182
|
-
|
|
183
|
-
const hasErrors = Object.values(errors).some(
|
|
184
|
-
(err) => err && err.length > 0,
|
|
185
|
-
);
|
|
214
|
+
## Hooks
|
|
186
215
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
216
|
+
| Hook | Description |
|
|
217
|
+
|------|-------------|
|
|
218
|
+
| `useFormDTO(dto, options?)` | Creates a form instance bound to a `FormDTO`. Extracts defaults and wires all validations automatically. |
|
|
219
|
+
| `useForm(options?)` | Lower-level hook for plain (non-DTO) forms with a react-hook-form–style API. |
|
|
220
|
+
| `useFormContext()` | Reads the form instance from the nearest `<FormProvider>`. Throws if no provider found. |
|
|
221
|
+
| `useOptionalFormContext()` | Like `useFormContext` but returns `undefined` instead of throwing. |
|
|
222
|
+
| `useWatch(name)` | Reactively reads one or more field values. Re-renders on change. |
|
|
223
|
+
| `useFieldArray({ name })` | Manages an array field — `fields`, `append`, `prepend`, `remove`, `swap`, `insert`. |
|
|
224
|
+
| `useFormBuilderController(props)` | Wraps `FormBuilder` in a ref-based controller. Returns a `Form` component + imperative API. |
|
|
225
|
+
| `useFormBuilder(dto)` | Low-level hook that manages raw form state without a provider. |
|
|
191
226
|
|
|
192
|
-
|
|
193
|
-
};
|
|
227
|
+
---
|
|
194
228
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
229
|
+
## Validation
|
|
230
|
+
|
|
231
|
+
Validation rules live on each field's `validations` object and are run automatically by `handleSubmit` or on demand via `trigger()`.
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
{
|
|
235
|
+
id: "email",
|
|
236
|
+
type: "email",
|
|
237
|
+
label: "Email",
|
|
238
|
+
validations: {
|
|
239
|
+
required: "Email is required",
|
|
240
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
241
|
+
minLength: 5,
|
|
242
|
+
maxLength: 100,
|
|
243
|
+
},
|
|
202
244
|
}
|
|
203
245
|
```
|
|
204
246
|
|
|
205
|
-
###
|
|
247
|
+
### Built-in rules
|
|
248
|
+
|
|
249
|
+
| Rule | Type | Description |
|
|
250
|
+
|------|------|-------------|
|
|
251
|
+
| `required` | `boolean \| string` | Field must not be empty. |
|
|
252
|
+
| `minLength` | `number` | Minimum string length. |
|
|
253
|
+
| `maxLength` | `number` | Maximum string length. |
|
|
254
|
+
| `min` | `number` | Minimum numeric value. |
|
|
255
|
+
| `max` | `number` | Maximum numeric value. |
|
|
256
|
+
| `pattern` | `RegExp` | Must match regex. |
|
|
257
|
+
| `validate` | `(value, allValues) => string \| null` | Custom function. |
|
|
258
|
+
|
|
259
|
+
### Validate programmatically
|
|
206
260
|
|
|
207
261
|
```ts
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
renderers: {
|
|
212
|
-
// optional: override field types
|
|
213
|
-
text: MyTextField,
|
|
214
|
-
},
|
|
215
|
-
handleChangeCallback: (id, value) => {
|
|
216
|
-
// optional: side effects on every field change
|
|
217
|
-
console.log(id, value);
|
|
218
|
-
},
|
|
219
|
-
});
|
|
262
|
+
const isValid = form.trigger(); // validate all fields
|
|
263
|
+
const emailOk = form.trigger('email'); // validate one field
|
|
264
|
+
```
|
|
220
265
|
|
|
221
|
-
|
|
222
|
-
controller.getValues(); // Record<string, any>
|
|
223
|
-
controller.getErrors(); // Record<string, string | null>
|
|
224
|
-
controller.validateAll(); // Record<string, string[] | null>
|
|
225
|
-
controller.validateField('id'); // string[]
|
|
266
|
+
### Inject server errors
|
|
226
267
|
|
|
227
|
-
|
|
228
|
-
|
|
268
|
+
```ts
|
|
269
|
+
const onSubmit = async (data) => {
|
|
270
|
+
try {
|
|
271
|
+
await api.save(data);
|
|
272
|
+
} catch {
|
|
273
|
+
form.setError('email', 'This email is already taken');
|
|
274
|
+
}
|
|
275
|
+
};
|
|
229
276
|
```
|
|
230
277
|
|
|
231
|
-
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## useFormBuilderController — Quick drop-in
|
|
281
|
+
|
|
282
|
+
For cases where you want the full form rendered from a DTO with an external imperative API and no `FormProvider` setup:
|
|
232
283
|
|
|
233
284
|
```tsx
|
|
234
285
|
import { useFormBuilderController } from 'react-form-dto';
|
|
235
|
-
import {
|
|
236
|
-
import { TextInput, SelectInput } from './customFields';
|
|
237
|
-
|
|
238
|
-
const renderers = {
|
|
239
|
-
text: TextInput,
|
|
240
|
-
select: SelectInput,
|
|
241
|
-
};
|
|
286
|
+
import { profileForm } from './profileForm';
|
|
242
287
|
|
|
243
|
-
function
|
|
244
|
-
const
|
|
245
|
-
dto: myFormDTO,
|
|
246
|
-
locale: 'en',
|
|
247
|
-
renderers,
|
|
248
|
-
handleChangeCallback: (id, value) => {
|
|
249
|
-
// sync with external store / analytics, etc.
|
|
250
|
-
console.log('Changed:', id, value);
|
|
251
|
-
},
|
|
252
|
-
});
|
|
288
|
+
function MyForm() {
|
|
289
|
+
const ctrl = useFormBuilderController({ dto: profileForm, locale: 'en' });
|
|
253
290
|
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
291
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
const errors = ctrl.validateAll();
|
|
294
|
+
if (Object.values(errors).every((e) => !e?.length)) {
|
|
295
|
+
console.log(ctrl.getValues());
|
|
296
|
+
}
|
|
257
297
|
};
|
|
258
298
|
|
|
259
299
|
return (
|
|
260
|
-
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
<form
|
|
266
|
-
onSubmit={(e) => {
|
|
267
|
-
e.preventDefault();
|
|
268
|
-
const errors = formController.validateAll();
|
|
269
|
-
const hasErrors = Object.values(errors).some(
|
|
270
|
-
(err) => err && err.length > 0,
|
|
271
|
-
);
|
|
272
|
-
if (!hasErrors) {
|
|
273
|
-
console.log('Values:', formController.getValues());
|
|
274
|
-
}
|
|
275
|
-
}}
|
|
276
|
-
>
|
|
277
|
-
<formController.Form />
|
|
278
|
-
<button type="submit">Submit</button>
|
|
279
|
-
</form>
|
|
280
|
-
</>
|
|
300
|
+
<form onSubmit={handleSubmit}>
|
|
301
|
+
<ctrl.Form />
|
|
302
|
+
<button type="submit">Submit</button>
|
|
303
|
+
</form>
|
|
281
304
|
);
|
|
282
305
|
}
|
|
283
306
|
```
|
|
284
307
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
308
|
+
| Method | Returns | Description |
|
|
309
|
+
|--------|---------|-------------|
|
|
310
|
+
| `getValues()` | `Record<string, any>` | All current field values. |
|
|
311
|
+
| `getErrors()` | `Record<string, string \| null>` | Current error state. |
|
|
312
|
+
| `validateAll()` | `Record<string, string[]>` | Validates every field. |
|
|
313
|
+
| `validateField(id)` | `string[]` | Validates one field. |
|
|
314
|
+
| `handleChange(id, val)` | `void` | Programmatically set a field value. |
|
|
315
|
+
| `Form` | `() => JSX.Element` | Auto-rendered form component. |
|
|
288
316
|
|
|
289
317
|
---
|
|
290
318
|
|
|
@@ -292,19 +320,19 @@ function AdvancedForm() {
|
|
|
292
320
|
|
|
293
321
|

|
|
294
322
|
|
|
295
|
-
The form in the image above is generated from this DTO
|
|
323
|
+
The form in the image above is generated from this DTO:
|
|
296
324
|
|
|
297
325
|
```tsx
|
|
298
326
|
const profileForm: FormDTO = {
|
|
299
327
|
title: "User Profile",
|
|
300
328
|
description: "Fill out your personal information",
|
|
301
|
-
layout: { cols: 12, gap: "1rem" },
|
|
329
|
+
layout: { cols: 12, gap: "1rem" },
|
|
302
330
|
sections: [
|
|
303
331
|
{
|
|
304
332
|
id: "personal",
|
|
305
333
|
heading: "Personal Information",
|
|
306
334
|
description: "Basic details about you",
|
|
307
|
-
layout: { cols: 12, gap: "1rem" },
|
|
335
|
+
layout: { cols: 12, gap: "1rem" },
|
|
308
336
|
fields: [
|
|
309
337
|
{
|
|
310
338
|
id: "title",
|
|
@@ -343,20 +371,12 @@ const profileForm: FormDTO = {
|
|
|
343
371
|
type: "multi-autocomplete",
|
|
344
372
|
label: "Skills",
|
|
345
373
|
placeholder: "Select your skills",
|
|
346
|
-
options: [
|
|
347
|
-
"React",
|
|
348
|
-
"TypeScript",
|
|
349
|
-
"Node.js",
|
|
350
|
-
"GraphQL",
|
|
351
|
-
"Docker",
|
|
352
|
-
],
|
|
374
|
+
options: ["React", "TypeScript", "Node.js", "GraphQL", "Docker"],
|
|
353
375
|
layout: { cols: 12 },
|
|
354
376
|
validations: {
|
|
355
377
|
required: "Select at least one skill",
|
|
356
378
|
validate: (val: string[]) =>
|
|
357
|
-
val && val.length < 2
|
|
358
|
-
? "Pick at least 2 skills"
|
|
359
|
-
: null,
|
|
379
|
+
val && val.length < 2 ? "Pick at least 2 skills" : null,
|
|
360
380
|
},
|
|
361
381
|
},
|
|
362
382
|
],
|
|
@@ -418,11 +438,7 @@ const profileForm: FormDTO = {
|
|
|
418
438
|
|
|
419
439
|
## 🎭 Conditional Visibility with `visibleWhen`
|
|
420
440
|
|
|
421
|
-
React Form DTO supports dynamic field visibility based on the values of other fields
|
|
422
|
-
|
|
423
|
-
### Basic Usage
|
|
424
|
-
|
|
425
|
-
Show a field only when another field has a specific value:
|
|
441
|
+
React Form DTO supports dynamic field visibility based on the values of other fields. Define simple conditions or complex AND/OR groups:
|
|
426
442
|
|
|
427
443
|
```tsx
|
|
428
444
|
{
|
|
@@ -436,189 +452,61 @@ Show a field only when another field has a specific value:
|
|
|
436
452
|
}
|
|
437
453
|
```
|
|
438
454
|
|
|
439
|
-
For
|
|
455
|
+
For full documentation see [Docs → visibleWhen](https://shakir-afridi.github.io/react-form-dto/docs/api/visibleWhen.html).
|
|
440
456
|
|
|
441
457
|
---
|
|
442
458
|
|
|
443
459
|
## 🌍 Internationalization (I18n)
|
|
444
460
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
### Using I18String
|
|
448
|
-
|
|
449
|
-
Any text property (`label`, `placeholder`, `title`, `description`, validation messages) can be either a plain string or a locale map:
|
|
461
|
+
Any text property (`label`, `placeholder`, `title`, `description`, validation messages) can be a plain string or a locale map:
|
|
450
462
|
|
|
451
463
|
```tsx
|
|
452
|
-
// Simple string (single language)
|
|
453
|
-
{
|
|
454
|
-
label: "First Name"
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// Multi-language support
|
|
458
464
|
{
|
|
459
465
|
label: {
|
|
460
466
|
en: "First Name",
|
|
461
467
|
fr: "Prénom",
|
|
462
|
-
es: "Nombre",
|
|
463
468
|
de: "Vorname"
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
For select, autocomplete, and multi-autocomplete fields, you can use `I18nOption` objects to provide translatable labels while maintaining stable values:
|
|
471
|
-
|
|
472
|
-
```tsx
|
|
473
|
-
{
|
|
474
|
-
id: "country",
|
|
475
|
-
type: "select",
|
|
476
|
-
label: { en: "Country", fr: "Pays" },
|
|
477
|
-
options: [
|
|
478
|
-
{
|
|
479
|
-
value: "us",
|
|
480
|
-
label: { en: "United States", fr: "États-Unis" }
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
value: "fr",
|
|
484
|
-
label: { en: "France", fr: "France" }
|
|
485
|
-
},
|
|
486
|
-
{
|
|
487
|
-
value: "de",
|
|
488
|
-
label: { en: "Germany", fr: "Allemagne" }
|
|
469
|
+
},
|
|
470
|
+
validations: {
|
|
471
|
+
required: {
|
|
472
|
+
en: "First name is required",
|
|
473
|
+
fr: "Le prénom est obligatoire"
|
|
489
474
|
}
|
|
490
|
-
|
|
475
|
+
}
|
|
491
476
|
}
|
|
492
477
|
```
|
|
493
478
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
```tsx
|
|
497
|
-
options: ["USA", "France", "Germany"] // Still supported
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
### I18n Validation Messages
|
|
479
|
+
Pass `locale` to `useFormDTO` to resolve messages:
|
|
501
480
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
```tsx
|
|
505
|
-
validations: {
|
|
506
|
-
required: {
|
|
507
|
-
en: "This field is required",
|
|
508
|
-
fr: "Ce champ est obligatoire",
|
|
509
|
-
es: "Este campo es obligatorio"
|
|
510
|
-
},
|
|
511
|
-
validate: (value) => value.length < 2
|
|
512
|
-
? {
|
|
513
|
-
en: "Minimum 2 characters",
|
|
514
|
-
fr: "Minimum 2 caractères"
|
|
515
|
-
}
|
|
516
|
-
: null
|
|
517
|
-
}
|
|
481
|
+
```ts
|
|
482
|
+
const form = useFormDTO(myDTO, { locale: 'fr' });
|
|
518
483
|
```
|
|
519
484
|
|
|
520
|
-
|
|
485
|
+
For select/autocomplete fields, use `I18nOption` objects for translatable option labels:
|
|
521
486
|
|
|
522
487
|
```tsx
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
es: "Registro de Usuario"
|
|
528
|
-
},
|
|
529
|
-
description: {
|
|
530
|
-
en: "Please fill in your details",
|
|
531
|
-
fr: "Veuillez remplir vos coordonnées",
|
|
532
|
-
es: "Por favor complete sus datos"
|
|
533
|
-
},
|
|
534
|
-
sections: [
|
|
535
|
-
{
|
|
536
|
-
id: "personal",
|
|
537
|
-
heading: {
|
|
538
|
-
en: "Personal Information",
|
|
539
|
-
fr: "Informations personnelles",
|
|
540
|
-
es: "Información Personal"
|
|
541
|
-
},
|
|
542
|
-
fields: [
|
|
543
|
-
{
|
|
544
|
-
id: "title",
|
|
545
|
-
type: "select",
|
|
546
|
-
label: { en: "Title", fr: "Titre", es: "Título" },
|
|
547
|
-
options: [
|
|
548
|
-
{ value: "mr", label: { en: "Mr", fr: "M.", es: "Sr." } },
|
|
549
|
-
{ value: "ms", label: { en: "Ms", fr: "Mme", es: "Sra." } },
|
|
550
|
-
{ value: "dr", label: { en: "Dr", fr: "Dr", es: "Dr." } }
|
|
551
|
-
]
|
|
552
|
-
},
|
|
553
|
-
{
|
|
554
|
-
id: "firstName",
|
|
555
|
-
type: "text",
|
|
556
|
-
label: { en: "First Name", fr: "Prénom", es: "Nombre" },
|
|
557
|
-
placeholder: {
|
|
558
|
-
en: "Enter your first name",
|
|
559
|
-
fr: "Entrez votre prénom",
|
|
560
|
-
es: "Ingrese su nombre"
|
|
561
|
-
},
|
|
562
|
-
validations: {
|
|
563
|
-
required: {
|
|
564
|
-
en: "First name is required",
|
|
565
|
-
fr: "Le prénom est obligatoire",
|
|
566
|
-
es: "El nombre es obligatorio"
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
]
|
|
571
|
-
}
|
|
572
|
-
]
|
|
573
|
-
};
|
|
488
|
+
options: [
|
|
489
|
+
{ value: "us", label: { en: "United States", fr: "États-Unis" } },
|
|
490
|
+
{ value: "de", label: { en: "Germany", fr: "Allemagne" } }
|
|
491
|
+
]
|
|
574
492
|
```
|
|
575
493
|
|
|
576
|
-
> **Note:** The library provides the I18n structure. You'll need to implement locale selection and text resolution in your application layer.
|
|
577
|
-
|
|
578
494
|
---
|
|
579
495
|
|
|
580
496
|
## Custom Field Renderers
|
|
581
497
|
|
|
582
|
-
Override any default renderer by supplying your own component:
|
|
583
|
-
|
|
584
|
-
```ts
|
|
585
|
-
{
|
|
586
|
-
id: "salary",
|
|
587
|
-
type: "number",
|
|
588
|
-
label: "Salary",
|
|
589
|
-
renderer: CurrencyInput
|
|
590
|
-
}
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
This makes the library extensible without modifying core logic.
|
|
498
|
+
Override any default renderer by supplying your own component via the `renderers` prop:
|
|
594
499
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
## Validation API
|
|
598
|
-
|
|
599
|
-
Validation is handled through the `useFormBuilder` hook and the imperative form handle.
|
|
600
|
-
|
|
601
|
-
```ts
|
|
602
|
-
const {
|
|
603
|
-
handleChange, // Function to update a field value: (id, value) => void
|
|
604
|
-
validateAll, // Function to validate all fields: () => Record<string, string[]>
|
|
605
|
-
getValues, // Function to get all current form values: () => Record<string, any>
|
|
606
|
-
getErrors, // Function to get all current form errors: () => Record<string, string | null>
|
|
607
|
-
validateField, // Function to validate a specific field: (id) => string[]
|
|
608
|
-
} = useFormBuilder(myFormDTO);
|
|
500
|
+
```tsx
|
|
501
|
+
<FormBuilder dto={myDTO} renderers={{ text: MyTextField, select: MySelect }} />
|
|
609
502
|
```
|
|
610
503
|
|
|
611
|
-
|
|
504
|
+
Or pass them through `useFormBuilderController`:
|
|
612
505
|
|
|
613
506
|
```ts
|
|
614
|
-
|
|
615
|
-
required: "This field is required",
|
|
616
|
-
validate: (value) => value.length < 2 ? "Minimum 2 characters" : null
|
|
617
|
-
}
|
|
507
|
+
const ctrl = useFormBuilderController({ dto: myDTO, locale: 'en', renderers: { text: MyTextField } });
|
|
618
508
|
```
|
|
619
509
|
|
|
620
|
-
> Recommendation: Standardize validation return values to `string[]` for predictable handling in large applications.
|
|
621
|
-
|
|
622
510
|
---
|
|
623
511
|
|
|
624
512
|
## Real-World Enterprise Usage
|
|
@@ -678,11 +566,11 @@ This enables:
|
|
|
678
566
|
Contributions are welcome and encouraged.
|
|
679
567
|
|
|
680
568
|
1. Fork the repository
|
|
681
|
-
2. Create a feature branch
|
|
569
|
+
2. Create a feature branch
|
|
682
570
|
`git checkout -b feature/my-feature`
|
|
683
|
-
3. Commit your changes
|
|
571
|
+
3. Commit your changes
|
|
684
572
|
`git commit -m "Add my feature"`
|
|
685
|
-
4. Push to the branch
|
|
573
|
+
4. Push to the branch
|
|
686
574
|
`git push origin feature/my-feature`
|
|
687
575
|
5. Open a Pull Request
|
|
688
576
|
|
|
@@ -696,4 +584,4 @@ MIT
|
|
|
696
584
|
|
|
697
585
|
---
|
|
698
586
|
|
|
699
|
-
|
|
587
|
+
## React Form DTO — Schema-first forms for Material UI
|