Better React Code Using Functional Programming
By Tom Norton
September 30th, 2020
We’ve all come across stuff like this before. You’ve got a component that can display errors locally, but calls external code:
import { someApiCall, someOtherApiCall } from './api'; const SubmitButtonGroup = () => { const toast = useToast(); return ( <> <Button onClick={someApiCall}> Call </Button> <Button onClick={someOtherApiCall}> Go </Button> <> ) }
Note: I’m being very generic here with my component names, and an imaginary toast hook based off the one from Chakra UI.
Looks fine, right?
But what if either of those calls fail?
You tell yourself you need to write an error handler. So you go ahead and write the following:
const onApiSubmit = async () => { try { await apiCall(); } catch (e) { toast('Oh no!', e.message, 'error'); } };
Then you add that to the local scope of your component. Not too bad. You’ve now handled that error and sorted it for the user. Then you realize that you’re going to have to do the same thing for that other API call. So you chug along and write the other wrapper:
const onOtherApiSubmit = async () => { try { await someOtherApiCall(); } catch (e) { toast('Oh no!', e.message, 'error'); } };
You’ve just basically written the same thing twice. Now picture the scene for three, or five buttons.
It’s going to be a long day.
Fortunately, we can do better, using a little concept from functional programming - known as a Higher-Order Function.
Higher Order Functions
According to Wikipedia, a Higher-Order Function is: “a function that does at least one of the following: takes one or more functions as arguments (i.e. procedural parameters), returns a function as its result”.
For example, if you’ve ever used map
, filter
or reduce
in JavaScript, you’ve used a Higher-Order Function. This is because they are functions you call, giving them a function to call on each element. The most classic example in the Web world is probably JavaScript’s setTimeout
:
const callback = () => alert('something happened!'); setTimeout(callback, 1000);
setTimeout
is a Higher-Order Function because it takes a callback as a parameter, a function to be executed later. This is a very common pattern we saw in the early days of JS.
Putting Higher-Order Functions To Work
So returning to our examples above:
const onApiSubmit = async () => { try { await apiCall(); } catch (e) { toast('Oh no!', e.message, 'error'); } }; const onOtherApiSubmit = async () => { try { await someOtherApiCall(); } catch (e) { toast('Oh no!', e.message, 'error'); } };
Can you spot the WET code here?
The only difference between both these wrappers is the actual API call that gets made. Every other part of those functions is the same. So what if we could make the wrapper into a Higher-Order Function? That way, the API function could be given as an argument to a single wrapper.
const withErrorHandling = (callback) => async () => { try { await callback(); } catch (e) { toast('Oh no!', e.message, 'error'); } };
There we go! A single wrapper for that can be used for as many actions as you want. So how do we use it? Simple! Let’s update our example from before:
import { someApiCall, someOtherApiCall } from './api'; const SubmitButtonGroup = () => { const toast = useToast(); const withErrorHandling = (callback) => async () => { try { await callback(); } catch(e) { toast('Oh no!', e.message, 'error'); } } return ( <> <Button onClick={withErrorHandling(someApiCall)}> Call </Button> <Button onClick={withErrorHandling(someOtherApiCall)}> Go </Button> <> ) }
Much better. One handler, that provides local error handling to external code. A solution that scales! 🙌
This is just a simple example to show how we can use functional programming on the Web to create concise, elegant applications. There are many more other things we can do in the same vein to make improvements, which I’ll cover in other posts.
I use functional programming daily as a React & React Native developer. In fact, I can’t remember the last time I actually wrote a for
loop. It is, in my opinion, one of many luxuries we web developers enjoy.
Thanks for reading!
💙