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
> While hooks are the future of Solid, higher-order components are still a powerful tool for composition. In particular, the API of `withForm` enables us to have strong type-safety without requiring users to pass generics.
218
+
219
+
## Reusing groups of fields in multiple forms
220
+
221
+
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.
222
+
223
+
> Unlike `withForm`, validators cannot be specified and could be any value.
224
+
> Ensure that your fields can accept unknown error types.
225
+
226
+
Rewriting the passwords example using `withFieldGroup` would look like this:
// These default values are not used at runtime, but the keys are needed for mapping purposes.
247
+
// This allows you to spread `formOptions` without needing to redeclare it.
248
+
const defaultValues:PasswordFields= {
249
+
password: '',
250
+
confirm_password: '',
251
+
}
252
+
253
+
const FieldGroupPasswordFields =withFieldGroup({
254
+
defaultValues,
255
+
// You may also restrict the group to only use forms that implement this submit meta.
256
+
// If none is provided, any form with the right defaultValues may use it.
257
+
// onSubmitMeta: { action: '' }
258
+
259
+
// Optional, but adds props to the `render` function in addition to `form`
260
+
props: {
261
+
// These default values are also for type-checking and are not used at runtime
262
+
title: 'Password',
263
+
},
264
+
// Internally, you will have access to a `group` instead of a `form`
265
+
render: function Render(props) {
266
+
// access reactive values using the group store
267
+
const password =useStore(
268
+
props.group.store,
269
+
(state) =>state.values.password,
270
+
)
271
+
// or the form itself
272
+
const isSubmitting =useStore(
273
+
props.group.form.store,
274
+
(state) =>state.isSubmitting,
275
+
)
276
+
277
+
return (
278
+
<div>
279
+
<h2>{props.title}</h2>
280
+
{/* Groups also have access to Field, Subscribe, Field, AppField and AppForm */}
281
+
<props.group.AppFieldname="password">
282
+
{(field) => <field.TextFieldlabel="Password" />}
283
+
</props.group.AppField>
284
+
<props.group.AppField
285
+
name="confirm_password"
286
+
validators={{
287
+
onChangeListenTo: ['password'],
288
+
onChange: ({ value, fieldApi }) => {
289
+
// The form could be any values, so it is typed as 'unknown'
290
+
const values:unknown=fieldApi.form.state.values
291
+
// use the group methods instead
292
+
if (value!==props.group.getFieldValue('password')) {
293
+
return'Passwords do not match'
294
+
}
295
+
returnundefined
296
+
},
297
+
}}
298
+
>
299
+
{(field) => (
300
+
<div>
301
+
<field.TextFieldlabel="Confirm Password" />
302
+
<field.ErrorInfo />
303
+
</div>
304
+
)}
305
+
</props.group.AppField>
306
+
</div>
307
+
)
308
+
},
309
+
})
310
+
```
216
311
217
-
While hooks are the future of Solid, higher-order components are still a powerful tool for composition. In particular, the API of `withForm` enables us to have strong type-safety without requiring users to pass generics.
312
+
We can now use these grouped fields in any form that implements the default values:
313
+
314
+
```tsx
315
+
// You are allowed to extend the group fields as long as the
316
+
// existing properties remain unchanged
317
+
typeAccount=PasswordFields& {
318
+
provider:string
319
+
username:string
320
+
}
321
+
322
+
// You may nest the group fields wherever you want
323
+
typeFormValues= {
324
+
name:string
325
+
age:number
326
+
account_data:PasswordFields
327
+
linked_accounts:Account[]
328
+
}
329
+
330
+
const defaultValues:FormValues= {
331
+
name: '',
332
+
age: 0,
333
+
account_data: {
334
+
password: '',
335
+
confirm_password: '',
336
+
},
337
+
linked_accounts: [
338
+
{
339
+
provider: 'TanStack',
340
+
username: '',
341
+
password: '',
342
+
confirm_password: '',
343
+
},
344
+
],
345
+
}
346
+
347
+
function App() {
348
+
const form =useAppForm(() => ({
349
+
defaultValues,
350
+
// If the group didn't specify an `onSubmitMeta` property,
351
+
// the form may implement any meta it wants.
352
+
// Otherwise, the meta must be defined and match.
353
+
onSubmitMeta: { action: '' },
354
+
}))
355
+
356
+
return (
357
+
<form.AppForm>
358
+
<FieldGroupPasswordFields
359
+
form={form}
360
+
// You must specify where the fields can be found
361
+
fields="account_data"
362
+
title="Passwords"
363
+
/>
364
+
<form.Fieldname="linked_accounts"mode="array">
365
+
{(field) =>
366
+
field().state.value.map((account, i) => (
367
+
<FieldGroupPasswordFields
368
+
key={account.provider}
369
+
form={form}
370
+
// The fields may be in nested fields
371
+
fields={`linked_accounts[${i}]`}
372
+
title={account.provider}
373
+
/>
374
+
))
375
+
}
376
+
</form.Field>
377
+
</form.AppForm>
378
+
)
379
+
}
380
+
```
381
+
382
+
### Mapping field group values to a different field
383
+
384
+
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
385
+
to their true location by changing the `field` property:
386
+
387
+
> [!IMPORTANT]
388
+
> 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.
389
+
390
+
```tsx
391
+
// To have an easier form, you can keep the fields on the top level
392
+
typeFormValues= {
393
+
name:string
394
+
age:number
395
+
password:string
396
+
confirm_password:string
397
+
}
398
+
399
+
const defaultValues:FormValues= {
400
+
name: '',
401
+
age: 0,
402
+
password: '',
403
+
confirm_password: '',
404
+
}
405
+
406
+
function App() {
407
+
const form =useAppForm(() => ({
408
+
defaultValues,
409
+
}))
410
+
411
+
return (
412
+
<form.AppForm>
413
+
<FieldGroupPasswordFields
414
+
form={form}
415
+
// You can map the fields to their equivalent deep key
416
+
fields={{
417
+
password: 'password',
418
+
confirm_password: 'confirm_password',
419
+
// or map them to differently named keys entirely
420
+
// 'password': 'name'
421
+
}}
422
+
title="Passwords"
423
+
/>
424
+
</form.AppForm>
425
+
)
426
+
}
427
+
```
428
+
429
+
If you expect your fields to always be at the top level of your form, you can create a quick map
0 commit comments