T
TanStack•3mo ago
fair-rose

dependent select dropdowns

I have a requirement where I need one form dropdown to trigger the options in another based on the selection. The common general use case for this would be the example of three dropdowns about location. The first is often the 'city', the second 'town' and the third would be the 'suburb'. Obviously the child options are not available until the parent has been selected. I use this pattern in react-hook-form and I now have it working with tanstack form, but I'm using listeners to achieve this. But it doesn't seem quite right. I was wondering if anyone has had any experience with this for tanstack form? This is the concept I have so far:
//the parent select
<form.AppField
listeners={{
onChange: ({ value }) => {
const subjects = subjectTree?.child?.find(subject => subject.code === value)
const children = subjects?.child
const options = children?.map(child => ({
label: child.name,
value: child.code,
}))
setSubjectOptions(options ?? [])
},
}}
children={field => (
<field.SelectField
selectItems={subfields}
/>
)}
/>

//the dependent select
<form.AppField
name='subject'
children={field => (
<field.SelectField
selectItems={subjectOptions}
/>
)}
/>
//the parent select
<form.AppField
listeners={{
onChange: ({ value }) => {
const subjects = subjectTree?.child?.find(subject => subject.code === value)
const children = subjects?.child
const options = children?.map(child => ({
label: child.name,
value: child.code,
}))
setSubjectOptions(options ?? [])
},
}}
children={field => (
<field.SelectField
selectItems={subfields}
/>
)}
/>

//the dependent select
<form.AppField
name='subject'
children={field => (
<field.SelectField
selectItems={subjectOptions}
/>
)}
/>
14 Replies
variable-lime
variable-lime•3mo ago
You'll probably need to add onChangeListenTo as well https://tanstack.com/form/latest/docs/framework/react/guides/linked-fields And you should take care that your depended fields get reset when the field they depend on changes (if I change the town, the suburb needs to reset)
Link Two Form Fields Together | TanStack Form React Docs
You may find yourself needing to link two fields together; when one is validated as another field's value has changed. One such usage is when you have both a password and confirmpassword field, where...
variable-lime
variable-lime•3mo ago
šŸ‘† this might help you, just paste it below your Selcts into your JSX and you can see the internals of your form (you'll want to watch the values of the form probably)
fair-rose
fair-roseOP•3mo ago
Thanks heaps. And is passing/setting state inside the listeners and options good practice?
fascinating-indigo
fascinating-indigo•3mo ago
if you have dependent state like this, there's not really a way around it. You could rely on useStore and useMemo to achieve the same result as you would with listeners, but that would no longer be within the JSX
fair-rose
fair-roseOP•3mo ago
Another option I tried was having an onchange that has a useEffect instead, and then the form.useStore to set the values
const {
state: { value },
handleChange,
} = useField({
form,
name: 'city',
})

const setSuburb = form.useStore((s) => s.state.values.suburb)

React.useEffect(() => {
form.updateFieldValue('suburb', '')
}, [value])
const {
state: { value },
handleChange,
} = useField({
form,
name: 'city',
})

const setSuburb = form.useStore((s) => s.state.values.suburb)

React.useEffect(() => {
form.updateFieldValue('suburb', '')
}, [value])
But I prefered the listener ... after I read more of the docs. It's a bit light on examples as why I'm here. Not feeling fully confident about it (my code) so far
fascinating-indigo
fascinating-indigo•3mo ago
Here's my suggestion in case you run into it in the future:
// Scenario 1: External state needs to be calculated (suburb available options)
const city = useStore(form.store, state => state.values.city)

const availableSuburbs = useMemo(() => {
// some calculation with city to determine suburbs
}, [city])

// Scenario 2: Form state dependent on other form state (suburb field value)
// Do what ksgn recommended
<form.Field
name="city"
listeners={{
onChange: ({ fieldApi }) => {
// reset suburb if city changes
fieldApi.form.setFieldValue('suburb', null)
}
}}
children={() => <></>}
/>
// Scenario 1: External state needs to be calculated (suburb available options)
const city = useStore(form.store, state => state.values.city)

const availableSuburbs = useMemo(() => {
// some calculation with city to determine suburbs
}, [city])

// Scenario 2: Form state dependent on other form state (suburb field value)
// Do what ksgn recommended
<form.Field
name="city"
listeners={{
onChange: ({ fieldApi }) => {
// reset suburb if city changes
fieldApi.form.setFieldValue('suburb', null)
}
}}
children={() => <></>}
/>
variable-lime
variable-lime•3mo ago
I like the State/City/Town/Suburb Example. Could be a good showcase for linked fields.
fascinating-indigo
fascinating-indigo•3mo ago
yeah, it's pretty good also a good example of external dependent state since that's very common for js frameworks
fair-rose
fair-roseOP•3mo ago
Yeah it took me a while to find the listener example and then play around inside. I understand the clean up stuff, I just left it out of my example (my bad) In RFH I use the watch hook
fascinating-indigo
fascinating-indigo•3mo ago
Tanstack Form's useStore and RHF's watch are very similar in usage
fair-rose
fair-roseOP•3mo ago
Out of curiosity, with your example, once the city has changed, then the available suburbs are calculated. how would I populate the available suburbs into the suburbs options? Is there an equivalent of resetting the default values of that field?
fascinating-indigo
fascinating-indigo•3mo ago
since suburb options are outside of the user's control, an external state is the best choice imo. The dropdown would map the suburbs array on render as for the actual selected suburb, if you want to fully reset the field (isTouched, isValid, value etc.), there's the resetField method
fair-rose
fair-roseOP•3mo ago
Thanks guys, very helpful. I will get back to my use case tomorrow
fair-rose
fair-roseOP•3mo ago
I found the example here (after checking linked fields) https://tanstack.com/form/latest/docs/framework/react/guides/basic-concepts#listeners It could be an ideal place to expand / link to another resource about building dependent dropdowns
Basic Concepts and Terminology | TanStack Form React Docs
Field statesThis page introduces the basic concepts and terminology used in the @tanstack/react-form library. Familiarizing yourself with these concepts will help you better understand and work with the library....

Did you find this page helpful?