Currying to Separate Concerns in React

Photo by emy on Unsplash

Currying! It’s a concept I’ve run across but had little reason to use until recently, when I found myself in an interesting situation in my React codebase.

I was working on a fairly complex nested form. The example in this post is parallel to the production feature I was working on, but it is simplified and generalized for clarity.

Let’s say we have a form for a menu app. You can select a menu item from the dropdown. Then once you’ve added that item, you can add some info about dietary restrictions.

This form needs to manage several things. First, the list of menu items that have been selected. Then, a list of dietary restrictions for each item. We expect to be able to change the selection of dietary restrictions for each menu item. (In a production example, we’d expect to be able to add and remove them too, but we’ll keep it scoped here.)

Let’s also say, as was the case for me, that we’re relying on some shared components to build this form. We’re using a select component that takes options for the dropdown and passes the selected option up to the parent onChange. This is how React Select works, which was the library we were using in my codebase, but you can imagine other shared components from internal or external libraries would work similarly — namely that the component cares about the specific option that’s being changed. But it doesn’t (and shouldn’t!) care about the bigger context that it’s rendered in, for example that it’s changing a dietary restriction option for a given menu item.

Let’s say the shared select has this interface:

// this is similar to what ReactSelect uses for its options
interface
ISelectOption {
value: string;
label: string;
}
interface ISharedSelectProps {
options: ISelectOption[];
selectedOption: ISelectOption;
onChange(option: ISelectOption): void;
}

And here’s how we’ll use the select in our menu component:

The key things to note in this example are that for each restriction option, we’re rendering a SharedSelect, and onChange of that option, we need to change the state for our form. However, as you can see on lines 97–104, the code as it stands results in a TypeScript error. We want to call a change handler that needs to know about the selected option as well as which restriction for which menu item was changed. But our SharedSelect expects a function that only takes the select option. The TS error, Expected 1 arguments but got 3 is our clue that currying can help us out here.

Let’s back up for a second and remind ourselves what currying* is. Currying is not specific to Javascript, but can be used in any functional programming language, or any language that allows you to program functionally. A quick Google search will tell you that currying is a way of transforming a function that takes several arguments into a series of nested functions that each take one argument.

In isolation, I’ve always found this definition to be very abstract and not very useful. But actually, this is exactly what we need here! We need our function to take just one argument.

So here’s what it looks like:

Functional programming nerds will be quick to point out that withIdentifyingData is not technically an example of currying; it’s actually an example of partial application. A true curry would transform our handleChange function into three functions that each take one argument, while a partial application transforms a function with multiple arguments per call. But since I’m just a humble web-dev and not a logician, I’m using “currying” in a looser sense here, as I think many web-devs do.

In our sample code, one concern is that our MenuForm needs to know which restriction to update for which menu item. The other concern is that the Select component needs to know which option is selected.

Currying allows us to keep those concerns separate. We know that when you pick a different option from the select menu, the form data state needs to update. There is no way to send the information about which menu item and which of its restrictions needs to change into the select component unless we change that component’s interface. If we own the component, we could make that change, but we shouldn’t do it. Changing the interface would mean coupling the select component to this specific use case, or accommodating an ever-growing list of use cases through optional arguments to the onChange function, making the code less and less coherent. If we don’t own the select component, as would be the case if we used an external library like React Select, then we couldn’t change the interface even if we wanted to.

Currying allows us to keep this shared component encapsulated but get the functionality we need from the form. Win-win.

*Like me, you may wonder where the word currying comes from. I recently learned that the word comes from the eponymous Haskell Curry, the mathematician after which the functional programming language Haskell is also named.

--

--

Software developer; language enthusiast

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store