When you start writing a component you normally define all the functions and other utils in the same file, but as the code grows you want to move them into an isolated file to keep your code cleaner.
But what if one of your functions takes a parameter that you won’t be able to import? It could be a state that you defined, or some data that you have fetched from an API.
Well, this can be solved with Function Currying.
What is Function Currying?
To summarize, it is a way to transform a function into single argument functions, a common example is this multiply method:
// original function
const multiply = (a, b) => {
return a * b
}
multiply(2, 4) // returns 8
// function currying
const curryMultiply = (a) => {
const multiply = (b) => {
return a * b
}
return multiply
}
curryMultiply(2)(4) // returns 8
The parameters are divided for each function, and this will be useful for our following example.
The component
A few weeks ago I had to implement a query builder and decided to use a popular library called react-query-builder, a highly customizable tool for building queries with various features, including grouping, a combination of different rules, and field validation.
What I needed to build was a module where users with certain roles in the application could filter customer profiles using different criteria defined in runtime. These criteria were concerned with customer profile information but also with data in other associated tables.
Architecturally, the application is divided into 2: a React frontend and a Ruby on Rails backend (an API-only Ruby on Rails app). The backend would perform the query built in the frontend but also it would serve the frontend the collection of attributes
the user would be able to select with their corresponding types
and operators
. Describing it in words, the backend could say: “You can query the date of birth, which is a date, and the operators are greater than, equal to, less than, etc”.
The purpose of the React Query Builder is to create the user query in a format the backend can understand. Once the query is processed by the backend, it returns the filtered data.
The problem
Despite having a list of operators for each attribute, I could only have one type of input value per attribute. For example, a field called reservation_date
will always have a date
type of value, and in this case, I have the operators equals
, greater than
, less than
, etc. But for the same attribute one might want to query if the reservation_date
is before x days ago
, or days from now
. In this case, we don’t want to enter a date, we want to enter a number.
React Query Builder is prepared for situations like this, as it provides a function called getInputType
, which determines which input should be rendered when selecting a certain field. However, the problem was that it works based on the attribute rather than the operator, so I had to make some modifications to the code.
export default function App() {
const [fields, setFields] = useState(searchFields)
const getInputType = (field, operator) => {
// Do something when the field and operator are selected...
}
return (
<div className="App">
<QueryBuilder fields={fields} getInputType={getInputType} />
</div>
)
}
Additionally, this function requires two parameters: field
and operator
. To keep my component clean and organized, I wanted to define my custom function in a separate file, but there were two issues to address:
- I needed access to my list of fields inside this function to determine the appropriate input type
- I couldn’t import the fields from the component to this file as they were stored in a state
This is where Function Currying became helpful.
Solution
Let’s start with the fields, the format of each parameter will be the following:
const fields = [
{
name: "reservation_date",
label: "Reservation",
operators: [
{
name: "eq",
label: "=",
valueType: "date",
},
{
name: "days_from_now",
label: "Days from now",
valueType: "number", // if the date is relative, we want this to be a number
}]
}
]
As you can see, the reservation_date
field has an operators
array and each operator has a different valueType
. Now, let’s see the getInputType
function that we will create for handling these operators:
// function to set the prop type of input (date, text...)
const getInputType = (field, operator) => {
const value = fields.find((f) => f.name === field)
if (!value) return null
const valueType = value.operators.find((op) => op.name === operator)?.valueType
return valueType ?? null
}
That looks great! but as I mentioned before there is a small problem, I can’t get the fields
without breaking the original function’s logic. We can solve this issue by wrapping it inside another function that will take the parameter I need and return this exact function.
export const defineGetInputType = (fields) => {
// function to set the prop type of input (date, text...)
const getInputType = (field, operator) => {
const value = fields.find((f) => f.name === field)
if (!value) return null
const valueType = value.operators.find((op) => op.name === operator)?.valueType
return valueType ?? null
}
return getInputType
}
And that’s it, now I’m able to export this and use it wherever I want. Here is the code in action:
Conclusion
Function currying is a great solution for when you need to handle parameters that are outside the lexical scope. In my case, I was able to move all my functions to a utils.js
file to keep my component more organized, this not only improved readability but also enhanced clarity in the code.
Now it’s your turn to try!