Cascading dropdowns is a popular paradigm, especially when dealing with search or filters. The gist is that making a selection in a dropdown list will load a subset of a greater dataset into another dropdown. This can carry on through several other dropdown lists until there’s no more data to filter, or the user submits the form.
When working with a database-backed form, this usually involves making several conditional database calls using data from previous dropdown selections. A good example would be postal codes in the U.S. The first dropdown would allow you to pick a state, which would filter the contents of the second dropdown to display only those postal codes that are present in that state.
Because my experiment with dropdowns in React revolves around a need to have this kind of cascade, I wanted to talk about how we can make this happen. Also because I’m in the throes of making it work for our internal applications, I won’t be posting screenshots, but I’ll see about getting pseudocode in place for visual reference where applicable.
Individual Components
Right now, I’m working with the idea that each dropdown is represented by its own React component. This was my original idea, and I think it’s the most flexible should the components have use elsewhere in the app or in another app.
Since I’ve already talked about how to get this single dropdown component working, I’m not going to rehash the setup. Instead, consider that we have our original dropdown — fed by an API that provides the data from which we build the <option> tags — and a second dropdown that operates in exactly the same way, but fed from a different API.
In order to make these two work together, we have to add a few conditionals to the definition of the second dropdown. Going forward, we’ll only be talking about the second dropdown component, which I’ll refer to as “the component”
The very first thing we need to do is work on the component’s useEffect event handler like so:
useEffect(() => {
const _id = props.selectedID;
id (_id !== undefined) {
APIAccess(URI...OTHERARGS);
}
}, [props.selectedID]);
selectedID is the attribute we’ll pass into this component to tell it what ID value was selected in the first dropdown. We assign it to a local var _id for convenience. We also do a quick check on the viability of _id because we do not want this API call to fire the first time we render the dropdown. Remember that useEffect fires whenever the DOM refreshes, including after the very first time we display the component. Because we won’t have a value for _id on the first render, we skip the API call.
Also of note is the final []. In the previous post I mentioned that we’ve added the empty array as the second parameter of useEffect so that the handler will fire only after the first render and never again. It’s useful for the ID dropdown list because it’s unfiltered and won’t add or remove items, but this behavior is the opposite of what we want for this dropdown. Instead, we put props.selectedID in there. What this does is tell useEffect to fire only when the current value of selectedID differs from the previous value of the same prop. The span of time is one re-render; if the value of selectedID doesn’t change between re-renders, then this useEffect will not fire at all. If we make another ID selection, however, it will, and because we’ll pass a value at that time, the API call will also fire. Bonus points: If we pass in a primed value of selectedID because, say, we are loading a complete form from the database (like a stored search), useEffect will fire because A) we have a value for selectedID and B) it’s changed from no previous value to having a value.
Basically, that’s kind of the broad strokes for this component. I will say that I cleaned up some logic in the building of the dropdown itself. Rather than trying to determine which <option> is going to get the “selected” attribute, I use the props.selectedID assigned to the <select>’s value property, which is cleaner.
Now we have to head up to the App level. Because the operation of the new dropdown works very much like the original dropdown, we have to add a new state variable to hold the selection and need a new onChange event handler that is passed as an attribute of the rendered tag. The new component instance will get an additional attribute selectedID which will contain the state value of what was selected in the ID dropdown.
Single Component
Now, I don’t have this coded, but theoretically, this should work.
While creating individual dropdown components is useful if you anticipate having to use them as individuals elsewhere, sometimes you have a very specific need for a cascade that you know won’t need to be replicated anywhere else except as a complete entity. In a case like this, it might be better to create a composite cascade component that exposes all of the dropdowns and surfaces the final collection of selected values when the user presses a button.
In theory, this shouldn’t be too hard; we’d dupe the render of dropdowns and, relying on an internal state, display <options> based on previous selections. We’d have to expand the internal state system, handle some of the events internally rather than call them from App, and only accept one event handler from App so we can get the data from each dropdown up to the App level when the user was ready to submit her selections.
One thing to mention is that the use of useEffect isn’t limited to one-and-done. A single component can “separate concerns” meaning we don’t need to cram all code into one handler; we can create as many as we need with each focusing on different logic for different “needs” of the component. We can have one useEffect that deals with API call for the IDs, and another useEffect that deals with the API call for the ID’s dependent data, for example. In fact, we could copy the two useEffect statements from the working individual component version and paste them into one file, and they should work exactly as intended.