DEV Community

Cover image for Here Are 7 Ways Higher Order Functions Can Improve Your Life
jsmanifest
jsmanifest

Posted on • Edited on • Originally published at jsmanifest.com

Here Are 7 Ways Higher Order Functions Can Improve Your Life

Find me on medium.

One of the fundamental building blocks of JavaScript are functions. That's enough to intrigue anyone about the semantics of functions when learning JavaScript.

But functions are much more intriguing than that.

"Like what", you ask?

They can become higher order functions--functions that take other functions as arguments, or functions that return functions as their output.

There's a whole lot of functions going on in that line--and JavaScript is no different. In JavaScript, functions are everywhere. And you should feel blessed that they are!

Here are 7 Ways Reasons Higher Order Functions Can Improve Your Life:

1. They Enhance Your Existing Code, Increasing Confidence

higher order functions can enhance your existing code

Imagine a function who's task is to retrieve a list of frogs from an API, uses the returned list to calculate the average width of tongues of the frogs and returns the result.

An example would be something like:

import axios from 'axios'

const getFrogs = async (params) => {
  try {
    const response = await axios.get(
      'https://frogs-and-their-tongues.com',
      params,
    )
    const frogs = response.data.result
    return frogs
  } catch (error) {
    throw error
  }
}

const calcAverageWidthOfTongues = async (params) => {
  try {
    const frogs = await getFrogs(params)
    const tongueWidths = frogs.reduce((sum, frog) => {
      return sum + frog.tongueWidth
    }, 0)
    const averageWidth = tongueWidths / frogs.length
    return averageWidth
  } catch (error) {
    throw error
  }
}

calcAverageWidthOfTongues({
  username: 'bob',
  password: 'the_builder100',
})
  .then((result) => {
    console.log(result)
  })
  .catch((error) => {
    console.error(error)
  })
Enter fullscreen mode Exit fullscreen mode

As it is now, we are restricted to only returning a number. But what if there was a way to transform it into an object without changing the original function?

The enhancement here is having the ability to pass in additional options to customize and transform the output for better control:

import axios from 'axios'

const getFrogs = async (params) => {
  try {
    const frogs = await axios.get('https://frogs-and-their-tongues.com', params)
    return data
  } catch (error) {
    throw error
  }
}

const calcAverageWidthOfTongues = async (params) => {
  try {
    const frogs = await getFrogs(params)
    const tongueWidths = frogs.reduce((sum, frog) => {
      return sum + frog.tongueWidth
    }, 0)
    const averageWidth = tongueWidths / frogs.length
    return averageWidth
  } catch (error) {
    throw error
  }
}

const useTongueObj = (fn, options) => {
  return async (params) => {
    const newParams = { ...params }
    if (options.limit !== undefined) {
      newParams.limit = options.limit
    }

    let averageWidth = await fn(newParams)

    if (typeof options.multiplyBy === 'number') {
      averageWidth = averageWidth * options.multiplyBy
    }

    return {
      averageWidth,
      multiplied: typeof options.multiplyBy === 'number',
      size: averageWidth < 2 ? 'small' : 'large', // size in inches
    }
  }
}

const calcTongueWidths = useTongueObj(calcAverageWidthOfTongues, {
  multiplyBy: 2,
})

calcTongueWidths({ limit: 10 })
  .then((tongueObj) => {
    console.log(tongueObj)
    /*
      result:
        {
          averageWidth: 8,
          multiplied: true,
          size: 'large'
        }
    */
  })
  .catch((error) => {
    console.log(result)
  })
Enter fullscreen mode Exit fullscreen mode

2. They Save Precious, Valuable Time

higher order functions can help save precious and valuable time

Let me give a real life example as I'd like to put a little more emphasize on this one.

One of the greatest benefits of higher order functions is that when used correctly, it will save a lot of time for you and for those around you.

At my 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' }}>
        &nbsp;&nbsp;
        <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,
  })
}
Enter fullscreen mode Exit fullscreen mode

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 />
Enter fullscreen mode Exit fullscreen mode

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' }}>
        &nbsp;&nbsp;
        <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
Enter fullscreen mode Exit fullscreen mode

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.

3. They Give The Ability To Create "Private Worlds"

higher order functions can create private worlds

What do I mean by "private worlds"?

Well, consider this example:

const preparePeopleWithFavoriteColor = (color) => {
  const _people = []

  return {
    getPeople() {
      return _people
    },
    addPeople(people) {
      _people.push(...people)
    },
    addPerson(person) {
      _people.push(person)
    },
    gather(people) {
      if (Array.isArray(people)) {
        people.forEach((person) => {
          if (color === person.favoriteColor) {
            _people.push(person)
          }
        })
      }
    },
  }
}

const peopleWhoLoveRed = preparePeopleWithFavoriteColor('red')

axios
  .get('https://someapi.com/peoples')
  .then((response) => {
    const people = response.data.result

    if (people.length) {
      peopleWhoLoveRed.gather(people)
    }

    return axios
      .get('https://someapi.com/other-peoples')
      .then((response) => {
        const morePeople = response.data.result
        if (morePeople.length) {
          everyoneWhoLovesRed.gather(morePeople)
        }
        return
      })
      .then(() => {
        // finally, add me last because i love red too
        peopleWhoLoveRed.addPerson({
          nickName: 'jsmanifest',
          favoriteColor: 'red',
        })

        return axios.post('https://api.redlovers.com/v1/subscribers/', {
          people: peopleWhoLoveRed.getPeople(),
        })
      })
  })
  .catch((error) => {
    console.error(error)
  })
Enter fullscreen mode Exit fullscreen mode

In the snippet, preparePeopleWithFavoriteColor creates a private world inside its block when called before returning the next function back to the caller. This "private world" is free of polluting the outer scope with clashing namecases and keeps its own private variables and values.

In addition, it creates its own interface and local api to manage its people list. The scope outside will never know what happens inside--and the only way for them to do so depends on the public methods that it returns.

And if you're sneaky enough, you can sneak in a secret API call in these cute little blocks to send you the list of people who love red everytime the code runs--and users will not even be able to tell because it didn't affect anything else in the app.

4. They Can Be Used As Quick And Hacky Solutions, Temporarily Decreasing Pressure

In the most darkest times, higher order functions can often save your life as it can be the quickest way to solve code issues without any noticeable changes in your app.

I once had a problem where users somehow still saw a "Dashboard" button in their navigation bar even after they signed out in our react app. The dashboard button should have only been visible if the app detects that they were already logged in. When they click on the dashboard button, they get redirected to their user dashboard page.

At the time, the way that they signed out was to head over to their profile page and click the log out button. When the button was clicked, they should have been logged out completely and get redirected back to the signin page.

But why was the dashboard button still showing in the navigation bar? The user signed out, and when I debugged the issue, everything in the client including the local storage was cleared out as it should have--or so I thought.

I then realized that the issue was some parts of the redux state was still there. The redirection was using a navigate that was optimized to redirect the user to a different route without refreshing the whole page. This caused caching issues.

So how would I then make sure that the entire state in redux resets after the user logs out?

Upon logging out we have redux dispatching an action creator with the action type LOGOUT which should signal to the app that the user is logging out.

My immediate focus to providing a solution was modifying the action creator somehow. It seemed like the best spot to provide that enhancement.

And then I realized that it wasn't the best solution, because there was a better solution: use a higher order function (but not here).

Here is a great example of why higher order functions are so powerful in JavaScript:

Our root reducer file in src/reducers/index.js exported this:

export default combineReducers({
  app,
  form: formReducer,
})
Enter fullscreen mode Exit fullscreen mode

We then applied a higher order function to instead wrap this and applied a reset state logic whenever it detects that an action with type LOGOUT is dispatched:

const appReducer = combineReducers({
  app,
  form: formReducer,
})

// Higher order reducer that resets the redux state when we dispatch a logout action
const rootReducer = (state, action) => {
  if (['LOGOUT'].includes(action.type)) {
    state = undefined
  }
  return appReducer(state, action)
}

export default rootReducer
Enter fullscreen mode Exit fullscreen mode

This resets the state back to its initial state because we re-assigned state to undefined. Redux will call the reducer with an undefined state and returns the initial state of the app.

5. It Can Hold, Manipulate, And Pass The Manipulated Data Anywhere, Giving You An Easy Time Testing Different Solutions

higher order functions can help manipulate and pass data around

One of the coolest things you can do with higher order functions is keeping a private cache of data, manipulating it and passing it anywhere in the app as you please. None of it will get tampered with from the outside.

For example, if you wanted to find a place to store access tokens so that when the token expires within 30 minutes in the middle of the user's session, you can refresh that token and re-set it for further use:

const Api = function(params) {
  const _store_ = {
    accessToken: null,
  }

  return {
    getAccessToken() {
      return _store.accessToken
    },
    async login() {
      try {
        const response = await axios.post(
          'https://something.com/v1/auth',
          params,
        )
        return response.data
      } catch (error) {
        throw error
      }
    },
    async refreshToken() {
      try {
        const response = await axios.post(
          'https://something.com/v1/access_token/',
          params,
        )
        const { token } = response.data
        _store.accessToken = token
        return token
      } catch (error) {
        throw error
      }
    },
    setAccessToken(token) {
      if (token === undefined) {
        throw new Error('token is undefined')
      }
      _store.accessToken = token
    },
    // ...other methods
  }
}

const api = Api({
  username: 'bob',
  password: 'the_builder123',
})

api
  .refreshToken())
  .then((token) => console.log(token))
  .catch(console.error)
Enter fullscreen mode Exit fullscreen mode

6. It Gives You The Ability To Create New Versions Of Something

Lets say you decided to create an RPG game to hand over to your 4 old nephew as an attempt to keep him from bugging you every day. In this game you decided you want to create a batch of warriors:

const characters = []

const Warrior = function createWarrior(name) {
  this.name = name
  this.hp = 100
  this.mp = 100
  this.defence = 60

  return {
    // Slash the enemy, decreasing their hp down by 35
    slash(target) {
      target.hp -= 35
      return target
    },
    // Increases the warrior's defence by 100 for 40 seconds.
    // Each second will decrement their defence by 1 as the time runs out.
    battleCry() {
      this.defence += 100
      this.battleCryInterval = setInterval(() => {
        this.defence -= 1
      }, 1000)
      this.battleCryTimeout = setTimeout(() => {
        this.defence = 60
      }, 40000)
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

You can create a higher order function that first accepts a list of names to create the warriors for, then returns a new function that helps create the warriors:

const massCreateWarriors = function(names) {
  return (onCreate) => {
    const warriors = []
    names.forEach((name) => {
      const newWarrior = new Warrior(name)
      if (onCreate) onCreate(newWarrior)
      warriors.push(newWarrior)
    })
    return warriors
  }
}

const prepareWarriors = massCreateWarriors(['bob', 'joe', 'sally', 'woodie'])

const newWarriors = prepareWarriors(function onCreate(newWarrior) {
  if (newWarrior.name === 'sally') {
    newWarrior.theme = 'red'
  }
  characters.push(newWarrior)
})
Enter fullscreen mode Exit fullscreen mode

Isn't it nice to just create a helper function to create a mass amount of characters for you, instead of having to hard code that every time for different characters?

7. It Can Help You Develop A Healthy Relationship With Your Boss And Your Co-workers

higher order functions can help you develop a healthy relationship with your boss and co workers

Higher order functions can help solve many problems. With that said, with the many useful benefits that higher order functions can bring to the table like code size reduction and re-usability, you will decrease the likelihood of stressing over people around you when used that way. Your co-workers will love that you are not just a developer, but a developer that strives to do great things. In addition, you also *increase* the likelihood that newer developers can learn off your code to help them improve their skills. This makes you highly valuable to the team, the boss, and the company as a whole.

... and when your coworkers are happy, they'll most likely be in the mood to bring in free donuts.

Conclusion

And this concludes the end of the post! I hope you enjoyed it and look out for more posts from me in the future! Find me on medium

Happy friday!

Top comments (0)