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

[WIP] Shared Component Library Docs #825

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

crutchcorn
Copy link
Member

This PR:

  • Adds an example of moving TanStack Form into your own shared component library
  • Adds the docs to explain what the example is doing

Copy link

nx-cloud bot commented Jul 8, 2024

☁️ Nx Cloud Report

We didn't find any information for the current pull request with the commit 2adadbf.
Please verify you are running the latest version of the NxCloud runner.

Check the Nx Cloud Source Control Integration documentation for more information.

Alternatively, you can contact us at [email protected].


Sent with 💌 from NxCloud.

Copy link

pkg-pr-new bot commented Jul 8, 2024

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

commit: bb26093

@tanstack/angular-form

npm i https://pkg.pr.new/@tanstack/angular-form@825

@tanstack/form-core

npm i https://pkg.pr.new/@tanstack/form-core@825

@tanstack/lit-form

npm i https://pkg.pr.new/@tanstack/lit-form@825

@tanstack/react-form

npm i https://pkg.pr.new/@tanstack/react-form@825

@tanstack/solid-form

npm i https://pkg.pr.new/@tanstack/solid-form@825

@tanstack/valibot-form-adapter

npm i https://pkg.pr.new/@tanstack/valibot-form-adapter@825

@tanstack/vue-form

npm i https://pkg.pr.new/@tanstack/vue-form@825

@tanstack/yup-form-adapter

npm i https://pkg.pr.new/@tanstack/yup-form-adapter@825

@tanstack/zod-form-adapter

npm i https://pkg.pr.new/@tanstack/zod-form-adapter@825


templates

@izakfilmalter
Copy link

One thing I am running into with this branch, field.state.value seems to always be string. If you have something else as the value you run into a world of pain. What is the best course of action for field values that are not string?

@Balastrong
Copy link
Member

Balastrong commented Nov 26, 2024

One thing I am running into with this branch, field.state.value seems to always be string. If you have something else as the value you run into a world of pain. What is the best course of action for field values that are not string?

How are you defining your component?

function TextInputField<
  TFormData extends unknown,
  TName extends DeepKeyValueName<TFormData, string>,
>

Here you can change string to your data type

@izakfilmalter
Copy link

CleanShot 2024-11-26 at 17 31 09

@izakfilmalter
Copy link

Ended up doing this, which is stupid, but works.

type ExpirationSelectFieldProps<
  Key extends string,
  TFormData extends { [K in Key]: ExpirationItem | null },
  TName extends DeepKeyValueName<TFormData, ExpirationItem>,
> = Omit<FieldOptions<TFormData, TName>, 'name'> & {
  name: Key
  form: ReactFormApi<TFormData, any> & {
    state: FormState<TFormData>
  }
  selectProps?: Omit<ComponentProps<typeof Select>, 'onChange' | 'value'> & {
    label?: string
    placeholder?: string
    inputWrapperClassName?: string
  }
  symbol: string
}

export function ExpirationSelectField<
  Key extends string,
  TFormData extends { [K in Key]: ExpirationItem | null },
  TName extends DeepKeyValueName<TFormData, ExpirationItem>,
>(props: ExpirationSelectFieldProps<Key, TFormData, TName>) { ... } 

@Meess
Copy link

Meess commented Dec 15, 2024

I was also working on integrating it more smoothly, i.e. abstracting the boiler plate and allowing to make custom components of it. This is my first POC, works for my side project now. Any feedback / suggestions / improvements are always welcome.

Usage:

// app/SomeForm.tsx
import { FieldInput, Form } from "../lib/form/Form"
export const SomeForm = () => {

    const form = useForm({
        defaultValues: {
            name: '',
            id: '',
        }
    })

    return (
        <Form onSubmit={form.handleSubmit}>
            <div className="flex flex-col space-y-2">
                <FieldInput formT={form} name="name" placeholder="Name" />
                <FieldInput formT={form} name="id" placeholder="ID" />
                <button type="submit">Add</button>
            </div>
        </Form>
    )
}

Abstract form components created to reduce boiler plate and allow for custom custom form components with @tanstack/form:

// lib/form/Form.tsx
import { DeepKeys, FieldApi, useForm } from '@tanstack/react-form'

export const Form: React.FC<React.FormHTMLAttributes<HTMLFormElement>> = ({
    children,
    ...props
}) => {
    return (
        <form
            {...props}
            onSubmit={(e) => {
                e.preventDefault()
                e.stopPropagation()
                if (props.onSubmit) {
                    props.onSubmit(e)
                }
            }}
        >
            {children}
        </form>
    )
}

export const FieldInfo = ({ field }: { field: FieldApi<any, any, any, any> }) => {
    return (
        <>
            {field.state.meta.isTouched && field.state.meta.errors.length ? (
                <em className=" text-red-500 text-xs">{field.state.meta.errors.join(',')}</em>
            ) : null}
            {field.state.meta.isValidating ? 'Validating...' : null}
        </>
    )
}

export const FieldInput = <TFormData, TNames extends DeepKeys<TFormData>>({
    formT,
    name,
    ...props
}: {
    formT: ReturnType<typeof useForm<TFormData, any>>
    name: TNames
} & React.InputHTMLAttributes<HTMLInputElement>) => (
    <formT.Field<TNames, any, any> name={name}>
        {(field) => (
            <>
                <input
                    value={field.state.value}
                    onChange={(e) => field.handleChange(e.target.value)}
                    onBlur={field.handleBlur}
                    className="w-full rounded-md border border-gray-300 px-4 py-2 transition duration-200 ease-in-out focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
                    {...props}
                />
                <FieldInfo field={field} />
            </>
        )}
    </formT.Field>
)

This works so far, but I might want to look into your solution @crutchcorn, with DeepKeyValueName, what's still going wrong (as it seems it's not merged)?

}

function TextInputField<
TFormData extends unknown,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anyone know why extends unknown constrains field.name to string instead of string | number?

@typescript-eslint/no-unnecessary-type-constraint and other sources I've found claim that an unconstrained generic type parameter already defaults to unknown, but this must not be true. Maybe some TS wizard will say "oh that's the extends unknown trick it fixes ..." 😅

Before I found this issue I had some of the implementation done myself but couldn't work out why DeepKeys doesn't exclude number, among other TS issues.

It's also a shame that form.Field<TName, any, string> doesn't work, because the types of field.state.value and field.handleChange are exactly where it would be useful to have TypeScript help.

@RazerM
Copy link

RazerM commented Jan 8, 2025

There's an issue with DeepKeyValueName when a nested object is nullable:

  interface Fields {
    nested: {
      name: string
      labels: string[]
    } | null
  }

  const form = useForm<Fields>({
    defaultValues: {
      nested: { name: '', labels: [] },
    },
  })

  return <TextInputField form={form} name="nested.name" />
Type '"nested.name"' is not assignable to type '`nested.labels[${number}]`'.

@crutchcorn
Copy link
Member Author

crutchcorn commented Jan 9, 2025

@RazerM can you open a different GH issue for the failing type error you're running into there?

@RazerM
Copy link

RazerM commented Jan 9, 2025

@crutchcorn it's unique to DeepKeyValueName added in this PR, <form.Field name={...} /> doesn't have the issue.

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.

6 participants