5 Examples of Higher Order Functions in JavaScript for Better Understanding
February 2nd, 2020
Higher order functions is arguably one of the best things that ever happened in the JavaScript language. They're used extensively for good reasons!
If you don't know what a higher order function is, they are simply functions that take other functions as arguments, or functions that return a function as their result.
When I was new to JavaScript, it took awhile for me to understand what they were, so if you're in this position then hopefully after going through this post you will have a clearer understanding of them.
With that said, here are 5 Examples of Higher Order Functions in JavaScript for Better Understanding:
.map
, .forEach
, etc.If you're new to JavaScript even for just a day, you've most likely already been working with higher order functions through native JavaScript array methods like .map
.
The snippet below loops over an array and invokes a function on each item until it has reached the last item. The capability of taking a function that it can invoke is what makes it a higher order function:
function prefixWordWithUnderscore(word) {
return `_${word}`
}
const words = ['coffee', 'apple', 'orange', 'phone', 'starbucks']
const prefixedWords = words.map(prefixWordWithUnderscore)
// result: ["_coffee", "_apple", "_orange", "_phone", "_starbucks"]
Sometimes we could end up deciding to create a higher order function so that we can use it to create more variations of a desired operation.
In the example below, a basic utilizePrefixer
function is a higher order function that returns a function that takes a word
as an argument and applies the prefix to the value that is being returned.
function utilizePrefixer(prefix) {
return function(word) {
return `${prefix}${word}`
}
}
const withMoneySign = utilizePrefixer('$')
const withCompanyName = utilizePrefixer('Fandango')
const tenDollars = withMoneySign('9.99')
const formHeader = withCompanyName(
' is the company you will be applying for today',
)
console.log(tenDollars)
console.log(formHeader)
Whenever you're in a situation where you need to refactor a bunch of code that is pointing towards one function you can create an enhanced function that can reverse the direction your code goes through.
I'm going to copy and paste an example from an older post because it's a solid example to get this point across:
At my previous job, we used react-toastify to display notifications. We used it every where. In addition, they also make great escape hatches for quick last minute UX decisions: "How should we handle this error? Just display a toast notification!" Done.
However, we started noticing that when the app became larger and the level of complexity was creeping up on us, our toast notifications were becoming too frequent. This is fine--however, we didn't have a way of preventing duplicates. This meant that some toast notifications were showing up multiple times on the screen even when they were exactly the same as the toast above it.
So we ended up leveraging the api that the library provides to help remove active toast notifications by id using toast.dismiss().
In order to explain the parts ahead, it's probably a good idea to show the file we were importing toasts from before proceeding:
import React from 'react'
import { GoCheck, GoAlert } from 'react-icons/go'
import { FaInfoCircle } from 'react-icons/fa'
import { MdPriorityHigh } from 'react-icons/md'
import { toast } from 'react-toastify'
/*
Calling these toasts most likely happens in the UI 100% of the time.
So it is safe to render components/elements as toasts.
*/
// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
// This used to show only one toast at a time so the user doesn't get spammed with toast popups
export const toastIds = {
// APP
internetOnline: 'internet-online',
internetOffline: 'internet-offline',
retryInternet: 'internet-retry',
}
// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
const getDefaultOptions = (options) => ({
position: toast && toast.POSITION.BOTTOM_RIGHT,
...options,
})
const Toast = ({ children, success, error, info, warning }) => {
let componentChildren
// Sometimes we are having an "object is not valid as a react child" error and children magically becomes an API error response, so we must use this fallback string
if (!React.isValidElement(children) && typeof children !== 'string') {
componentChildren = 'An error occurred'
} else {
componentChildren = children
}
let Icon = GoAlert
if (success) Icon = GoCheck
if (error) Icon = GoAlert
if (info) Icon = FaInfoCircle
if (warning) Icon = MdPriorityHigh
return (
<div style={{ paddingLeft: 10, display: 'flex', alignItems: 'center' }}>
<div style={{ width: 30, height: 30 }}>
<Icon style={{ color: '#fff', width: 30, height: 30 }} />
</div>
<div style={{ padding: 8, display: 'flex', alignItems: 'center' }}>
<span style={{ color: '#fff' }}>{componentChildren}</span>
</div>
</div>
)
}
export const success = (msg, opts) => {
return toast.success(<Toast success>{msg}</Toast>, {
className: 'toast-success',
...getDefaultOptions(),
...opts,
})
}
export const error = (msg, opts) => {
return toast.error(<Toast error>{msg}</Toast>, {
className: 'toast-error',
...getDefaultOptions(),
...opts,
})
}
export const info = (msg, opts) => {
return toast.info(<Toast info>{msg}</Toast>, {
className: 'toast-info',
...getDefaultOptions(),
...opts,
})
}
export const warn = (msg, opts) => {
return toast.warn(<Toast warning>{msg}</Toast>, {
className: 'toast-warn',
...getDefaultOptions(),
...opts,
})
}
export const neutral = (msg, opts) => {
return toast(<Toast warning>{msg}</Toast>, {
className: 'toast-default',
...getDefaultOptions(),
...opts,
})
}
Now bear with me, I know this might not look appealing. But I promise it will get better in two minutes.
This is what we had in a separate component to check if a previous toast was already on the screen. And if there was, it will attempt to remove that toast and re-display the new toast.
import { toast } from 'react-toastify'
import {
info as toastInfo,
success as toastSuccess,
toastIds,
} from 'util/toast'
const onOnline = () => {
if (toast.isActive(toastIds.internetOffline)) {
toast.dismiss(toastIds.internetOffline)
}
if (toast.isActive(toastIds.retryInternet)) {
toast.dismiss(toastIds.retryInternet)
}
if (!toast.isActive(toastIds.internetOnline)) {
toastSuccess('You are now reconnected to the internet.', {
position: 'bottom-center',
toastId: toastIds.internetOnline,
})
}
}
const onOffline = () => {
if (!toast.isActive(toastIds.internetOffline)) {
toastInfo('You are disconnected from the internet right now.', {
position: 'bottom-center',
autoClose: false,
toastId: toastIds.internetOffline,
})
}
}
useInternet({ onOnline, onOffline })
return <App />
This was working fine--however, we had other toasts throughout the app that needed to be modified the same way. We had to go through every file that displays a toast nofication to remove duplicates.
When we think of going through every file in 2019, we immediately knew that it wasn't the solution. So we looked at the util/toast.js
file and refactored that to solve our problem instead. Here's what it looked like afterwards:
src/util/toast.js
import React, { isValidElement } from 'react'
import isString from 'lodash/isString'
import isFunction from 'lodash/isFunction'
import { GoCheck, GoAlert } from 'react-icons/go'
import { FaInfoCircle } from 'react-icons/fa'
import { MdPriorityHigh } from 'react-icons/md'
import { toast } from 'react-toastify'
/*
Calling these toasts most likely happens in the UI 100% of the time.
So it is safe to render components/elements as toasts.
*/
// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
// This used to show only one toast at a time so the user doesn't get spammed with toast popups
export const toastIds = {
// APP
internetOnline: 'internet-online',
internetOffline: 'internet-offline',
retryInternet: 'internet-retry',
}
// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
const getDefaultOptions = (options) => ({
position: toast && toast.POSITION.BOTTOM_RIGHT,
...options,
})
const Toast = ({ children, success, error, info, warning }) => {
let componentChildren
// Sometimes we are having an "object is not valid as a react child" error and children magically becomes an API error response, so we must use this fallback string
if (!isValidElement(children) && !isString(children)) {
componentChildren = 'An error occurred'
} else {
componentChildren = children
}
let Icon = GoAlert
if (success) Icon = GoCheck
if (error) Icon = GoAlert
if (info) Icon = FaInfoCircle
if (warning) Icon = MdPriorityHigh
return (
<div style={{ paddingLeft: 10, display: 'flex', alignItems: 'center' }}>
<div style={{ width: 30, height: 30 }}>
<Icon style={{ color: '#fff', width: 30, height: 30 }} />
</div>
<div style={{ padding: 8, display: 'flex', alignItems: 'center' }}>
<span style={{ color: '#fff' }}>{componentChildren}</span>
</div>
</div>
)
}
const toaster = (function() {
// Attempt to remove a duplicate toast if it is on the screen
const ensurePreviousToastIsRemoved = (toastId) => {
if (toastId) {
if (toast.isActive(toastId)) {
toast.dismiss(toastId)
}
}
}
// Try to get the toast id if provided from options
const attemptGetToastId = (msg, opts) => {
let toastId
if (opts && isString(opts.toastId)) {
toastId = opts.toastId
} else if (isString(msg)) {
// We'll just make the string the id by default if its a string
toastId = msg
}
return toastId
}
const handleToast = (type) => (msg, opts) => {
const toastFn = toast[type]
if (isFunction(toastFn)) {
const toastProps = {}
let className = ''
const additionalOptions = {}
const toastId = attemptGetToastId(msg, opts)
if (toastId) additionalOptions.toastId = toastId
// Makes sure that the previous toast is removed by using the id, if its still on the screen
ensurePreviousToastIsRemoved(toastId)
// Apply the type of toast and its props
switch (type) {
case 'success':
toastProps.success = true
className = 'toast-success'
break
case 'error':
toastProps.error = true
className = 'toast-error'
break
case 'info':
toastProps.info = true
className = 'toast-info'
break
case 'warn':
toastProps.warning = true
className - 'toast-warn'
break
case 'neutral':
toastProps.warning = true
className - 'toast-default'
break
default:
className = 'toast-default'
break
}
toastFn(<Toast {...toastProps}>{msg}</Toast>, {
className,
...getDefaultOptions(),
...opts,
...additionalOptions,
})
}
}
return {
success: handleToast('success'),
error: handleToast('error'),
info: handleToast('info'),
warn: handleToast('warn'),
neutral: handleToast('neutral'),
}
})()
export const success = toaster.success
export const error = toaster.error
export const info = toaster.info
export const warn = toaster.warn
export const neutral = toaster.neutral
Instead of having to go through every file, the simplest solution was to create a higher order function. Doing this allowed us to "reverse" the roles so that instead of us searching through files, the toasts were instead directed to our higher order function.
This way the codes in the files were not modified or touched. They still function as normal, and we gained the ability to remove duplicate toasts without going anywhere to write unnecessary code in the end. This saved time.
Higher order functions begin to shine a lot more when you start to compose them. One can choose to compose for many reasons. For the example below, we compose for the reason that we want to prevent JavaScript from looping over the array of frogs over and over for each .filter
operation:
function createFilterers() {
const _filters = {
ids: [],
fns: {},
}
return {
addFilter(name, fn) {
_filters.ids.push(name)
_filters.fns[name] = fn
},
removeFilter(name) {
const index = _filters.ids.indexOf(name)
if (index !== -1) _filters.splice(index, 1)
delete _filters.fns[name]
},
filter(arr) {
const filters = _filters.ids.map((id) => _filters.fns[id])
return arr.reduce((acc, item) => {
for (let index = 0; index < _filters.ids.length; index++) {
const id = _filters.ids[index]
const filter = _filters.fns[id]
if (!filter(item)) {
return acc
}
}
return acc.concat(item)
}, [])
},
}
}
const frogs = [
{
name: 'bobTheFrog',
age: 2,
gender: 'male',
favoriteFood: 'fly',
weight: 5,
},
{
name: 'lisaTheFrog',
age: 3,
gender: 'female',
favoriteFood: 'fly',
weight: 1,
},
{
name: 'sallyTheFrog',
age: 10,
gender: 'female',
favoriteFood: 'caterpillar',
weight: 20,
},
{
name: 'mikeTheFrog',
age: 1,
gender: 'male',
favoriteFood: 'worm',
weight: 8,
},
{
name: 'georgeTheFrog',
age: 7,
gender: 'male',
favoriteFood: 'fly',
weight: 28,
},
{
name: 'kellyTheFrog',
age: 3,
gender: 'female',
favoriteFood: 'ladybug',
weight: 3,
},
]
const filterer = createFilterers()
filterer.addFilter('fat-frogs', (frog) => {
return frog.weight >= 8
})
filterer.addFilter('male-frogs', (frog) => {
return frog.gender === 'male'
})
const filteredFrogs = filterer.filter(frogs)
console.log(filteredFrogs)
This can prevent us from having to write repetitive code like this everywhere in our app:
const filteredFrogs = frogs
.filter((frog) => {
return frog.weight >= 8
})
.filter((frog) => {
return frog.gender === 'male'
})
.filter((frog) => {
return frog.name.startsWith('b')
})
It's worth mentioning that when we create a higher order function we can also create some local state inside the scope and it will be cached for future operations:
function createMyHigherOrderFunction(options) {
const state = { ...options } // Our local state object
return function(...args) {
return function(callback) {
return callback(state, ...args)
}
}
}
const prepare = createMyHigherOrderFunction({
name: 'bobby',
favoriteFood: 'steak',
})
const prepareWithArgs = prepare({ country: 'United States' })
const finalize = prepareWithArgs((state, options) => ({ ...state, ...options }))
console.log(finalize)
/*
result: {
country: "United States",
favoriteFood: "steak",
name: "bobby"
}
*/