T
TanStack6mo ago
genetic-orange

Custom ConditionalField

I am trying to simplify the way i handle conditional fields. For example i have a role field which can be set to either "User" | "Admin".
If "Admin" is set a new required field reason should appear. I have seen approaches where users add an onChange listener to the field role and it works fine. However this is a simple usecase. My actual use case is a big multi step form where the field might be rendered somewhere completely different. I generally don't like separating logic (In this case the listener) and rendering (of the field in some other subform later). I endeavered to replicate the listener approach with the help of form.Subscribe and came up with a simple custom form.ConditionalField:
type ConditionFn = (
// this does work however i lose typesafety when using it.
// <form.ConditionalField condition={(state) => state.values. <- no type completion
state: Parameters<
NonNullable<
Parameters<ReturnType<typeof useFormContext>['Subscribe']>[0]['selector']
>
>[0]
) => boolean;

function ConditionalField({
name,
children,
condition,
}: {
name: string; //<- How to type this
children: React.ReactNode;
condition: ConditionFn;
}) {
const form = useFormContext();
return (
<form.Subscribe selector={condition}>
{(isShown) => {
useEffect(() => {
if (!isShown) {
form.deleteField(name);
}
}, [isShown]);
if (!isShown) {
return null;
}
return children;
}}
</form.Subscribe>
);
}

export const { useAppForm, withForm } = createFormHook({
fieldComponents: {
TextField,
SelectField,
},
formComponents: {
ConditionalField,
},
fieldContext,
formContext,
});
type ConditionFn = (
// this does work however i lose typesafety when using it.
// <form.ConditionalField condition={(state) => state.values. <- no type completion
state: Parameters<
NonNullable<
Parameters<ReturnType<typeof useFormContext>['Subscribe']>[0]['selector']
>
>[0]
) => boolean;

function ConditionalField({
name,
children,
condition,
}: {
name: string; //<- How to type this
children: React.ReactNode;
condition: ConditionFn;
}) {
const form = useFormContext();
return (
<form.Subscribe selector={condition}>
{(isShown) => {
useEffect(() => {
if (!isShown) {
form.deleteField(name);
}
}, [isShown]);
if (!isShown) {
return null;
}
return children;
}}
</form.Subscribe>
);
}

export const { useAppForm, withForm } = createFormHook({
fieldComponents: {
TextField,
SelectField,
},
formComponents: {
ConditionalField,
},
fieldContext,
formContext,
});
As stated in above example i loose typesafety of the condition function and name prop. How can i correctly type it?
4 Replies
genetic-orange
genetic-orangeOP6mo ago
In a perfect world i would also wrap form.AppField and pass the name property through correctly aswell. however the form object returned by useFormContext doesn't have the AppField component
quickest-silver
quickest-silver6mo ago
can you put tsx so your code example gets more readable?
```tsx
```tsx
genetic-orange
genetic-orangeOP6mo ago
genetic-orange
genetic-orangeOP6mo ago
seems like for fieldComponents i can use the generic of useFieldContext<string>() to manually bridge this type gap. But formComponents/useFormContext has no way to provide a generic type.

Did you find this page helpful?