feat(react-form): add fieldOptions and dynamicFieldOptions APIs
Related to #1202 Add fieldOptions and dynamicFieldOptions APIs that allow creating fieldOptions outside of JSX/html...
Implementation:
export const interestFormOpts = formOptions({
defaultValues: {
fullName: '',
interests: [
{
name: '',
level: 0,
},
],
},
})
// static field options
const fullNameOptions = fieldOptions({
formOptions: { ...interestFormOpts },
name: 'fullName',
validators: {
onChange: ({ value }) => {
if (value === '') {
return 'Full name is required'
}
},
},
})
...
// dynamic field options
const interestLevelOptions = dynamicFieldOptions((idx: number) => ({ // parm can be of any type allowing dynamic usage!
formOptions: { ...interestFormOpts },
name: `interests[${idx}].level`,
listeners: {
onChange: ({ value, fieldApi }) => {
if (value > 9000) {
fieldApi.form.setFieldValue(`interests[${idx}].name`, 'Vegeta')
}
},
},
}))
export const fieldConfig = {
fullName: fullNameOptions,
interests: interestsOptions,
interestName: interestNameOptions,
interestLevel: interestLevelOptions,
}
Usage in form:
// static fieldOptions usage
<form.AppField
{...fieldConfig.fullName}
children={(field) => <field.TextField label="Full Name" />}
/>
...
// dynamic fieldOptions usage
{interestsField.state.value.map((_, idx) => (
...
<form.AppField
{...fieldConfig.interestLevel(idx)}
children={(interestLevelField) => (
<interestLevelField.TextField label="Interest Level" />
)}
/>
</div>
))}
Why?
-
Keep validation and listener logic out of JSX The primary advantage is the clean separation of form validation and field behavior from component rendering code. This allows for leaner JSX without the need to define the configuration inline.
-
Centralized Tanstack Form Fields Configuration Field configurations, validators, and behaviors can be defined in one central location, making it much easier to understand a form's complete behavior without navigating through scattered JSX files.
-
Dynamic Configuration Support The ability to define configurations as functions (with arbitrary parameters) enables dynamic field configurations as needed! Developers can create configurations that adapt based on indices (perfect for field arrays) or other variables.
-
All done with Type Safety With the implementation in this PR we provide strong TypeScript support with preserved type information throughout the configuration. This ensures that field-specific validators receive the correct value types and that configurations maintain their defined structure.
View your CI Pipeline Execution ↗ for commit 858201b699b7d208017afffda160fa80e3caf089.
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
❌ Failed | 3m 11s | View ↗ |
nx run-many --target=build --exclude=examples/** |
✅ Succeeded | 24s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-03-15 19:50:01 UTC
More templates
- @tanstack/form-example-angular-array
- @tanstack/form-example-angular-simple
- @tanstack/form-example-lit-simple
- @tanstack/form-example-lit-ui-libraries
- @tanstack/form-example-react-array
- @tanstack/form-example-react-compiler
- @tanstack/field-errors-from-form-validators
- @tanstack/form-example-react-large-form
- @tanstack/form-example-react-next-server-actions
- @tanstack/form-example-react-query-integration
- @tanstack/form-example-remix
- @tanstack/form-example-react-simple
- @tanstack/form-example-react-standard-schema
- @tanstack/form-example-react-tanstack-start
- @tanstack/form-example-react-ui-libraries
- @tanstack/form-example-solid-array
- @tanstack/form-example-solid-simple
- @tanstack/form-example-vue-array
- @tanstack/form-example-vue-simple
@tanstack/angular-form
npm i https://pkg.pr.new/@tanstack/angular-form@1201
@tanstack/form-core
npm i https://pkg.pr.new/@tanstack/form-core@1201
@tanstack/lit-form
npm i https://pkg.pr.new/@tanstack/lit-form@1201
@tanstack/react-form
npm i https://pkg.pr.new/@tanstack/react-form@1201
@tanstack/solid-form
npm i https://pkg.pr.new/@tanstack/solid-form@1201
@tanstack/vue-form
npm i https://pkg.pr.new/@tanstack/vue-form@1201
commit: 858201b
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 88.80%. Comparing base (
230c2a0) to head (4b33df3). Report is 5 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #1201 +/- ##
==========================================
+ Coverage 88.70% 88.80% +0.09%
==========================================
Files 28 29 +1
Lines 1257 1268 +11
Branches 329 331 +2
==========================================
+ Hits 1115 1126 +11
Misses 127 127
Partials 15 15
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
Added type and runtime tests, especially focused on making sure the errors object is still being properly inferred when any combo of form/field validations are used.
Additionally, originally I wanted to do a single override on fieldOptions* but that wouldn't be possible if we want to allow any dynamic parameter within the function override. Sadly TS won't find the matching override if we make it dynamic. I think keeping it separate might be cleaner and easier to debug/iterate on anyways.
Realized I had added a fieldOptions key inside the fieldOptions and dynamicFieldOptions APIs, removed it since unnecessary and aligns closer to what crutchcorn and I discussed here: https://discord.com/channels/719702312431386674/1100437019857014895/1345637762899902464
I think we should keep the formOptions key/value as it makes it simpler to then only pull out the remaining options we want like this:
export function fieldOptions<...>(options: { formOptions, .... }) {
//...
const { formOptions, ...fieldOpts } = options
return fieldOpts
}
Closing, will create new PR if other users find this API useful!