TanStack / TanStack/form

`withFieldGroup` field map type is too wide

Open
#1,855 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: bug v1
Dominant language
TypeScript
Stars
6.7k
Forks
682
Avg merge
5d 18h
Merged PRs (30d)
7

Description

Describe the bug

When types of reusable form component are wider than types of parent form - it is not longer safe to use...

Your minimal, reproducible example

https://stackblitz.com/edit/tanstack-form-zwgf8aqd?file=src%2Findex.tsx

Steps to reproduce

Run this code with React.js and click X button

import { createFormHook, createFormHookContexts } from "@tanstack/react-form"

const { fieldContext, formContext } =
  createFormHookContexts()

const { useAppForm, withFieldGroup } = createFormHook({
  fieldComponents: {},
  formComponents: {},
  fieldContext,
  formContext,
})

type PasswordFields = {
  password: string | null
}

const reusableDefaultValues: PasswordFields = { password: '', }

export const FieldGroupPasswordFields = withFieldGroup({
  defaultValues: reusableDefaultValues,
  render: function Render({ group }) {
    return (
      <div>
        <h2>Reusable Fields</h2>
        <group.AppField name="password">
          {(field) => <>
            <input
              value={field.state.value ?? ""}
              onChange={(e) => field.handleChange(e.target.value)}
            />
            <button onClick={() => field.handleChange(null)}>X</button>
          </>}
        </group.AppField>
      </div>
    )
  },
})


// ======================= App Form ======================

type FormValues = {
  reusable: { password: string }
}

const defaultValues: FormValues = {
  reusable: { password: '', },
}

export function App() {
  const form = useAppForm({ defaultValues })

  return (
    <form.AppForm>
      <FieldGroupPasswordFields
        form={form}
        fields="reusable"
      />
      <form.Subscribe
        selector={f => f.values.reusable.password}
        children={p => <div>
          {
            // Here it CRASHES when password is null
          }
          {p.startsWith("1") ? "Valid" : "Invalid"}
        </div>}
      />
    </form.AppForm>
  )
}

Expected behavior

I should see the TypeScript error when types do not match exactly!

How often does this bug happen?

None

Screenshots or Videos

No response

Platform

Does not matter

TanStack Form adapter

react-form

TanStack Form version

1.25.0

TypeScript version

5.9.3

Additional context

At least ts check like this should be sufficient for this issue:

type Equals<T, S> =
	[T] extends [S] ? (
		[S] extends [T] ? true : false
	) : false

Check this out:

// this function just reads the value
function read(arg: string) {}
read("" as ""); // valid, narrower type
read("" as string); // valid, exact type
// type 'string | null' is not assignable to parameter of type 'string'
read("" as string | null); // invalid, too wide

// this function just writes the value, type checking works in inverse way
function write(setter: (arg: string) => void) {}
// type '(arg: "") => void' is not assignable to parameter of type '(arg: string) => void'
write((arg: "") => {}); // invalid, too narrow
write((arg: string) => {}); // valid, exact type
write((arg: string | null) => {}); // valid, wider type


// this function both reads and writes the value, type checking is strict
function readWrite(arg: string, setter: (arg: string) => void) {}
readWrite("" as "", (arg: "") => {}); // invalid, too narrow for the setter
readWrite("" as string, (arg: string) => {}); // valid, exact type
readWrite("" as string | null, (arg: string | null) => {}); // invalid, too wide for the getter

As you can see for the read getter function wider types are not accepted which is perfectly valid, but narrower type are accepted.

And for the write setter function, vice versa narrower type are not accepted but wider types are accepted, which is also perfectly logical.

The readWrite function only accepts exact type, which is kinda intersection of both types.

The FieldGroupPasswordFields component is basically readWrite function, it can read the form field values and write to them. So to maintain type safety it should show typescript error when parent form part does not have exactly the same types for the fields.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the withFieldGroup types and reproduce the issue in the linked StackBlitz example using the shown React and TypeScript code. Trace how the reusable and parent field maps are compared; done means incompatible field types produce a TypeScript error before the form can crash when the password becomes null.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.