Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-form): add fieldOptions and dynamicFieldOptions APIs #1201

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

juanvilladev
Copy link
Contributor

@juanvilladev juanvilladev commented Mar 2, 2025

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?

  1. 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.

  2. 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.

  3. 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.

  4. 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.

Copy link

nx-cloud bot commented Mar 2, 2025

View your CI Pipeline Execution ↗ for commit 4b33df3.

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 25s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 22s View ↗

☁️ Nx Cloud last updated this comment at 2025-03-02 23:19:45 UTC

Copy link

pkg-pr-new bot commented Mar 2, 2025

@juanvilladev juanvilladev force-pushed the use_field_configurations branch from 5fd9403 to c002b5a Compare March 2, 2025 16:28
Copy link

codecov bot commented Mar 2, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

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              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@juanvilladev
Copy link
Contributor Author

juanvilladev commented Mar 2, 2025

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.

@juanvilladev juanvilladev marked this pull request as ready for review March 2, 2025 19:27
@juanvilladev
Copy link
Contributor Author

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
}

@juanvilladev juanvilladev changed the title feat(react-form): add ability to define field configurations outside JSX feat(react-form): add fieldOptions and dynamicFieldOptions APIs Mar 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant