form icon indicating copy to clipboard operation
form copied to clipboard

Records fields have wrong types

Open binajmen opened this issue 6 months ago • 1 comments

Describe the bug

I wonder if it is possible to have such field value with TanStack Form. Pretty sure it should (and is) but the types seem wrong:

function App() {
  const form = createForm(() => ({
    defaultValues: {
      firstName: '',
      lastName: '',
      address: {
        home: {
          street: '',
          number: '',
        },
      } as Record<string, { street: string; number: string }>,
    },
    ...
  }));

  return (
    <div>
      <h1>Simple Form Example</h1>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          e.stopPropagation();
          form.handleSubmit();
        }}
      >
        ...
        <div>
          <form.Field
            name={`address.${'home'}.street`}
            children={(field) => (
              <>
                <label for={field().name}>Street:</label>
                <input
                  id={field().name}
                  name={field().name}
                  value={field().state.value}
                  onBlur={field().handleBlur}
                  onInput={(e) => field().handleChange(e.target.value)}
                />
                <FieldInfo field={field()} />
              </>
            )}
          />
        </div>

In this example, I specify the field name name={`address.${'home'}.street`} where home is a variable in my real use case.

If you open this Stackblitz repro, you'll notice this does not work as expected.

There is no type issue on the name attribute of form.Field as it respect one of the expected value (address.${string}.street), although the type of the value itself is wrong:

(property) value: {
    street: string;
    number: string;
} & string

So is the handleChange definition.

I've discussed it on Discord and it seems this bug is confirmed.

Your minimal, reproducible example

https://stackblitz.com/edit/ts-form-object?file=src%2Findex.tsx&preset=node

Steps to reproduce

  1. Go to Stackblitz
  2. Hover the typescript errors

Expected behavior

Have the correct type ;)

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

macOS, Chrome

TanStack Form adapter

solid-form

TanStack Form version

1.6.3

TypeScript version

No response

Additional context

No response

binajmen avatar Jun 03 '25 15:06 binajmen

I had the exact same issue with this minimal use-case:

import * as React from 'react'
import { createRoot } from 'react-dom/client'

import { TanStackDevtools } from '@tanstack/react-devtools'
import { FormDevtoolsPlugin } from '@tanstack/react-form-devtools'
import { useForm } from '@tanstack/react-form'

export default function App() {
  const form = useForm({
    defaultValues: {
      fields: {} as Record<string, { name: string; value: string }>,
    },
    onSubmit: async ({ value }) => {
      // Do something with form data
      console.log(value)
    },
  })

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault()
        e.stopPropagation()
        form.handleSubmit()
      }}
    >
      <div>
        <form.Field
          name="fields.firstname.name"
          children={(field) => {
            return (
              <>
                <label htmlFor={field.name}>First Name:</label>
                <input
                  id={field.name}
                  name={field.name}
                  value={field.state.value}
                  onBlur={field.handleBlur}
                  onChange={(e) => {
                    return field.handleChange(e.target.value)
                  }}
                />
              </>
            )
          }}
        />
      </div>
      <form.Subscribe
        selector={(state) => [state.canSubmit, state.isSubmitting]}
        children={([canSubmit, isSubmitting]) => (
          <button type="submit" disabled={!canSubmit}>
            {isSubmitting ? '...' : 'Submit'}
          </button>
        )}
      />
    </form>
  )
}

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

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

    <TanStackDevtools
      config={{ hideUntilHover: true }}
      plugins={[FormDevtoolsPlugin()]}
    />
  </React.StrictMode>,
)

It works as expected, but the line return field.handleChange(e.target.value) triggers the following error:

Argument of type 'string' is not assignable to parameter of type 'Updater<{ name: string; value: string; } & string>'.ts(2345)

brmzkw avatar Oct 23 '25 20:10 brmzkw