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

Issues on migrating from final-form to @tanstack/react-form #1052

Closed
douglasjunior opened this issue Dec 3, 2024 · 1 comment
Closed

Issues on migrating from final-form to @tanstack/react-form #1052

douglasjunior opened this issue Dec 3, 2024 · 1 comment

Comments

@douglasjunior
Copy link

douglasjunior commented Dec 3, 2024

Describe the bug

I'm trying to migrate from final-form to @tanstack/react-form, but I struggled with some issues that I cant figure out how to solve at the moment.

  1. The first one may be a bug, but form.state.isValid starts with true even with invalid fields.

    Maybe related to canSubmit in form is true even if some fields are unvalid #358

    This can be reproduced even in the CodeSandbox example: https://codesandbox.io/p/devbox/boring-dust-crdfdk

    It's important to mention that I can't force the form submission or field validation because we need to keep the field as isTouched = false.

  2. Focused state in the field

    Final-form has a meta in the field to indicate that this field is on focus, in the same context, the form has an array indicating the focused fields.

    This is useful to handle some situations in complex screens and also to highlight components.

    Today the @tanstack/react-form only handles the blur event to update the isTouched state.

Your minimal, reproducible example

https://codesandbox.io/p/devbox/boring-dust-crdfdk

Steps to reproduce

Just render the app and take a look at the Form status.

Expected behavior

When the form mounts, the validations must be run to allow the form to update the isValid state.

How often does this bug happen?

Every time

Screenshots or Videos

Initial render:

Image

After blurring the field:

Image

After submit:

Image

Platform

  • OS: MacOS
  • Browser: Chrome
  • Version: Latest

TanStack Form adapter

react-form

TanStack Form version

0.37.1

TypeScript version

5.3.3

Additional context

No response

@douglasjunior douglasjunior changed the title Migrating from final-form to @tanstack/react-form Issues on migrating from final-form to @tanstack/react-form Dec 3, 2024
@crutchcorn
Copy link
Member

After updating to the newest version and moving your form.state.isValid into a form.Subscribe (alt: useStore(form.store, state => state.isValid), it works:

import type { AnyFieldApi } from '@tanstack/react-form';
import { useForm } from '@tanstack/react-form';
import * as React from 'react';
import { createRoot } from 'react-dom/client';

function FieldInfo({ field }: { field: AnyFieldApi }) {
  return (
    <>
      {field.state.meta.isTouched && field.state.meta.errors.length ? (
        <em>{field.state.meta.errors.join(',')}</em>
      ) : null}
      {field.state.meta.isValidating ? 'Validating...' : null}
    </>
  );
}

export default function App() {
  const form = useForm({
    defaultValues: {
      firstName: '',
      lastName: '',
    },
    onSubmit: async ({ value }) => {
      // Do something with form data
      console.log(value);
    },
  });

  return (
    <div>
      <h1>Simple Form Example</h1>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          e.stopPropagation();
          form.handleSubmit();
        }}
      >
        <div>
          {/* A type-safe field component*/}
          <form.Field
            name="firstName"
            validators={{
              onChange: ({ value }) =>
                !value
                  ? 'A first name is required'
                  : value.length < 3
                  ? 'First name must be at least 3 characters'
                  : undefined,
              onChangeAsyncDebounceMs: 500,
              onChangeAsync: async ({ value }) => {
                await new Promise((resolve) => setTimeout(resolve, 1000));
                return (
                  value.includes('error') && 'No "error" allowed in first name'
                );
              },
            }}
            children={(field) => {
              // Avoid hasty abstractions. Render props are great!
              return (
                <>
                  <label htmlFor={field.name}>First Name:</label>
                  <input
                    id={field.name}
                    name={field.name}
                    value={field.state.value}
                    onBlur={field.handleBlur}
                    onChange={(e) => field.handleChange(e.target.value)}
                  />
                  <FieldInfo field={field} />
                </>
              );
            }}
          />
        </div>
        <div>
          <form.Field
            name="lastName"
            children={(field) => (
              <>
                <label htmlFor={field.name}>Last Name:</label>
                <input
                  id={field.name}
                  name={field.name}
                  value={field.state.value}
                  onBlur={field.handleBlur}
                  onChange={(e) => field.handleChange(e.target.value)}
                />
                <FieldInfo field={field} />
              </>
            )}
          />
        </div>
        <form.Subscribe
          selector={(state) => [
            state.canSubmit,
            state.isSubmitting,
            state.isValid,
            state.isFieldsValid,
          ]}
          children={([canSubmit, isSubmitting, isValid, isFieldsValid]) => (
            <>
              <button type="submit" disabled={!canSubmit}>
                {isSubmitting ? '...' : 'Submit'}
              </button>
              <button type="reset" onClick={() => form.reset()}>
                Reset
              </button>
              <div>Form status: {isValid ? 'Valid' : 'Invalid'}</div>
              <div>Fields status: {isFieldsValid ? 'Valid' : 'Invalid'}</div>
            </>
          )}
        />
      </form>
    </div>
  );
}

const rootElement = document.getElementById('root')!;

createRoot(rootElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

You need form.Subscribe to get the latest values of form.state

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

No branches or pull requests

2 participants