T
TanStack•3mo ago
fascinating-indigo

How do you manage fields with the type `Record<string, ...>` ?

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>
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`}
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: https://stackblitz.com/edit/ts-form-object?file=src%2Findex.tsx&preset=node 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
(property) value: {
street: string;
number: string;
} & string
So is the handleChange definition. Am I doing something wrong? Is it not supported (and therefore should I need to change the structure of my form)?
Benjamin
StackBlitz
TSF Object - StackBlitz
Run official live example code for Form Simple, created by Tanstack on StackBlitz
20 Replies
fascinating-indigo
fascinating-indigoOP•3mo ago
This "works":
<form.Field
name={`address.${"home"}`}
children={(field) => (
<>
<label for={field().name}>Street:</label>
<input
id={field().name}
name={field().name}
value={field().state.value.street}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange({...field().state.value, street: e.target.value})}
/>
<FieldInfo field={field()} />
</>
)}
/>
<form.Field
name={`address.${"home"}`}
children={(field) => (
<>
<label for={field().name}>Street:</label>
<input
id={field().name}
name={field().name}
value={field().state.value.street}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange({...field().state.value, street: e.target.value})}
/>
<FieldInfo field={field()} />
</>
)}
/>
It seems the value and handleChange types match the first ${string} occurrence. Not sure if this is expected.
national-gold
national-gold•3mo ago
Records should be supported, and that type looks wrong. address.${'home'}.street should be a valid template string to enter
fascinating-indigo
fascinating-indigoOP•3mo ago
mmh does the Stackblitz highlights an issue then ?
national-gold
national-gold•3mo ago
I'll take a look. The stackblitz seems short enough, so a unit test should be easy to derive
fascinating-indigo
fascinating-indigoOP•3mo ago
awesome if required i can open an issue on github for you
national-gold
national-gold•3mo ago
this bug seems familiar ... the intersection with string I mean
fascinating-indigo
fascinating-indigoOP•3mo ago
yeah the intersection with both the type of address.home and address.home.street looks suspicious ;D
national-gold
national-gold•3mo ago
maybe it's solid-specific. I just checked my React prod implementation using a record and the type is just fine there
national-gold
national-gold•3mo ago
nope, it's not. Could you create a Github issue with your stackblitz reproduction, please? @binajmen
No description
fascinating-indigo
fascinating-indigoOP•3mo ago
sure I will !
fascinating-indigo
fascinating-indigoOP•3mo ago
GitHub
Records fields have wrong types · Issue #1551 · TanStack/form
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(() =&gt; (...
fascinating-indigo
fascinating-indigoOP•3mo ago
(I wasn't inspired for the name of the issue, sorry ^^)
national-gold
national-gold•3mo ago
thanks!
fascinating-indigo
fascinating-indigoOP•3mo ago
you're welcome, thank YOU for the nice work !
national-gold
national-gold•3mo ago
took some time analyzing this. The main issue is that TypeScript sees foo.${string}.bar as matching both foo.${string}.bar as well as foo.${string} so any subfield from a record gets caught in an intersection with the record value
fascinating-indigo
fascinating-indigoOP•3mo ago
yeah that's what I noticed you are mentioning typescript. does it means it's not solvable with the current typescript implementation?
national-gold
national-gold•3mo ago
no, it needs fixing the types
fascinating-indigo
fascinating-indigoOP•3mo ago
I can live with the type issue. my understanding is that the functionality itself exists in TS Form, which is good enough for me 😉 I can just cast for the time being
national-gold
national-gold•3mo ago
the reason it wasn't caught is because the iteration doesn't throw a type error, even though the type it generated can't be created For reference:
type Records = {
[x: `foo.${string}`]: {
bar: string;
};
[x: `foo.${string}.bar`]: string;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// '`foo.${string}.bar`' index type 'string' is not assignable to '`foo.${string}`' index type '{ bar: string; }'.
foo: Record<string, {
bar: string;
}>;
}
type Records = {
[x: `foo.${string}`]: {
bar: string;
};
[x: `foo.${string}.bar`]: string;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// '`foo.${string}.bar`' index type 'string' is not assignable to '`foo.${string}`' index type '{ bar: string; }'.
foo: Record<string, {
bar: string;
}>;
}
fascinating-indigo
fascinating-indigoOP•3mo ago
han

Did you find this page helpful?