You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat: add createFormGroup
* refactor: move createFormGroup to AppForm
While the previous separate implementation was compatible with AppForm, users
wouldn't have been able to use any field/form components
in the render itself. This commit allows them to do that,
at the expense of not being compatible with useForm.
* chore: add unit tests for createFormGroup types
* chore: add unit test for createFormGroup
* chore: export CreateFormGroupProps
* add DeepKeysOfType util type
This type will help with a lens API for forms
so that only names leading to the subset data
are allowed.
* feat: add initial FormLensApi draft
* chore: add FormLensApi tests batch
* fix(form-core): fix form.resetField() ignoring nested fields
* chore: complete form-core unit test for FormLensApi
* feat: add react adapter to form lens api
* fix: fix names for lens.Field and add test
* chore: export WithFormLensProps
* feat: add Subscribe and store to form lens
* feat: add mount method to FormLensApi
* fix: memoize innerProps to avoid focus loss in withFormLens
* refactor: use single useState instead of multiple useMemos
this is because useMemo is not intended for stable objects
see https://react.dev/reference/react/useMemo for more info
* feat: allow nesting withFormLenses
* remove createFormGroup for redundancy
* fix: widen typing of lens.Field/AppField to correct level
* docs: add withFormLens section
* fix: fix TName for lens component
* docs: fix typo in withFormLens
* feat: add lensErrors to FormLensApi store
* chore: adjust memo dependency in useFormLens
* chore: call userEvent.setup() in createFormHook tests
* refactor: move path concatenation to utils
* chore: move useLens to own file and rename
* feat: add FieldsMap and createFieldMap utils
* chore: migrate (most) lens references to field group
* chore: finalize migration from lens to field group
* ci: apply automated fixes and generate docs
* chore: remove accidental test file
* chore: add some unit tests for field mapping
* chore: add unit tests
* docs: update docs to use group
* docs: add caveat with field mapping
* docs: fix weird line break in alert text
* revert: remove FieldGroupApi#resetFieldMeta
the method appears to be a helper function for form.reset,
which is accessible from the field group API.
There does not seem to be a good reason to port this method
from FormApi.
* refactor: allow null or undefined for field group keys
users are able to do conditional rendering, but
TS generally doesn't pick up on it. Therefore,
while it is less type safe, it allows users to
use field groups in more locations than previously
possible.
* chore: add FieldGroupApi.test-d.ts
* docs(react-form): amend large form example with withFieldGroup
* chore(form-core): remove FieldGroupApi.reset
the reset is already accessible through group.form.reset.
* chore: fix broken unit test
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sometimes, a pair of fields are so closely related that it makes sense to group and reuse them — like the password example listed in the [linked fields guide](./linked-fields.md). Instead of repeating this logic across multiple forms, you can utilize the `withFieldGroup` higher-order component.
254
+
255
+
> Unlike `withForm`, validators cannot be specified and could be any value.
256
+
> Ensure that your fields can accept unknown error types.
257
+
258
+
Rewriting the passwords example using `withFieldGroup` would look like this:
{/* Groups also have access to Field, Subscribe, Field, AppField and AppForm */}
310
+
<group.AppFieldname="password">
311
+
{(field) => <field.TextFieldlabel="Password" />}
312
+
</group.AppField>
313
+
<group.AppField
314
+
name="confirm_password"
315
+
validators={{
316
+
onChangeListenTo: ['password'],
317
+
onChange: ({ value, fieldApi }) => {
318
+
// The form could be any values, so it is typed as 'unknown'
319
+
const values:unknown=fieldApi.form.state.values
320
+
// use the group methods instead
321
+
if (value!==group.getFieldValue('password')) {
322
+
return'Passwords do not match'
323
+
}
324
+
returnundefined
325
+
},
326
+
}}
327
+
>
328
+
{(field) => (
329
+
<div>
330
+
<field.TextFieldlabel="Confirm Password" />
331
+
<field.ErrorInfo />
332
+
</div>
333
+
)}
334
+
</group.AppField>
335
+
</div>
336
+
)
337
+
},
338
+
})
339
+
```
340
+
341
+
We can now use these grouped fields in any form that implements the default values:
342
+
343
+
```tsx
344
+
// You are allowed to extend the group fields as long as the
345
+
// existing properties remain unchanged
346
+
typeAccount=PasswordFields& {
347
+
provider:string
348
+
username:string
349
+
}
350
+
351
+
// You may nest the group fields wherever you want
352
+
typeFormValues= {
353
+
name:string
354
+
age:number
355
+
account_data:PasswordFields
356
+
linked_accounts:Account[]
357
+
}
358
+
359
+
const defaultValues:FormValues= {
360
+
name: '',
361
+
age: 0,
362
+
account_data: {
363
+
password: '',
364
+
confirm_password: '',
365
+
},
366
+
linked_accounts: [
367
+
{
368
+
provider: 'TanStack',
369
+
username: '',
370
+
password: '',
371
+
confirm_password: '',
372
+
},
373
+
],
374
+
}
375
+
376
+
function App() {
377
+
const form =useAppForm({
378
+
defaultValues,
379
+
// If the group didn't specify an `onSubmitMeta` property,
380
+
// the form may implement any meta it wants.
381
+
// Otherwise, the meta must be defined and match.
382
+
onSubmitMeta: { action: '' },
383
+
})
384
+
385
+
return (
386
+
<form.AppForm>
387
+
<PasswordFields
388
+
form={form}
389
+
// You must specify where the fields can be found
390
+
fields="account_data"
391
+
title="Passwords"
392
+
/>
393
+
<form.Fieldname="linked_accounts"mode="array">
394
+
{(field) =>
395
+
field.state.value.map((account, i) => (
396
+
<PasswordFields
397
+
key={account.provider}
398
+
form={form}
399
+
// The fields may be in nested fields
400
+
fields={`linked_accounts[${i}]`}
401
+
title={account.provider}
402
+
/>
403
+
))
404
+
}
405
+
</form.Field>
406
+
</form.AppForm>
407
+
)
408
+
}
409
+
```
410
+
411
+
### Mapping field group values to a different field
412
+
413
+
You may want to keep the password fields on the top level of your form, or rename the properties for clarity. You can map field group values
414
+
to their true location by changing the `field` property:
415
+
416
+
> [!IMPORTANT]
417
+
> Due to TypeScript limitations, field mapping is only allowed for objects. You can use records or arrays at the top level of a field group, but you will not be able to map the fields.
418
+
419
+
```tsx
420
+
// To have an easier form, you can keep the fields on the top level
421
+
typeFormValues= {
422
+
name:string
423
+
age:number
424
+
password:string
425
+
confirm_password:string
426
+
}
427
+
428
+
const defaultValues:FormValues= {
429
+
name: '',
430
+
age: 0,
431
+
password: '',
432
+
confirm_password: '',
433
+
}
434
+
435
+
function App() {
436
+
const form =useAppForm({
437
+
defaultValues,
438
+
})
439
+
440
+
return (
441
+
<form.AppForm>
442
+
<PasswordFields
443
+
form={form}
444
+
// You can map the fields to their equivalent deep key
445
+
fields={{
446
+
password: 'password',
447
+
confirm_password: 'confirm_password',
448
+
// or map them to differently named keys entirely
449
+
// 'password': 'name'
450
+
}}
451
+
title="Passwords"
452
+
/>
453
+
</form.AppForm>
454
+
)
455
+
}
456
+
```
457
+
458
+
If you expect your fields to always be at the top level of your form, you can create a quick map
While the above examples are great for getting started, they're not ideal for certain use-cases where you might have hundreds of form and field components.
0 commit comments