DEV Community

John Au-Yeung
John Au-Yeung

Posted on

Why is Lodash Still Useful?

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

Even more articles at http://thewebdev.info/

With the release of ES6 and later versions of JavaScript, there were lots of methods released with it, expanding the functionality of the language. For example, there are new array and string methods, as well as useful operators like the spread and rest operators.

However, JavaScript still doesn’t make utility libraries like Lodash obsolete because there are lots of useful methods that still aren’t available in JavaScript.

In this article, we’ll look at a few methods from Lodash that still makes development easier than with plain JavaScript, including the times, get, set, debounce, deburr, and keyBy methods.

_.times

The times method lets us call a function multiple times, put all the retuned results of each function call into an array, and return it.

It takes 2 arguments, which are the number of times that the function is called and the second is the function to call.

For example, we can use it as follows:

import * as _ from "lodash";
const getRandomInteger = () => Math.round(Math.random() * 100);  
let result = _.times(5, getRandomInteger);  
console.log(result);

Then we may get the following result:

[16, 83, 35, 87, 41]

_.debounce

We can use the debounce method to delay a call of a function by a specified amount of time. There’s no easy way to do this with JavaScript.

This is useful for event handling where we want to wait for something to be done and then call a function. For example, with a typeahead search, a debounce would wait until the user is finished typing before making the API call, removing unnecessary hits on your server.

For example, we can use it as follows. Given the following number input:

<input type="number" />

We can write the following JavaScript code:

import * as _ from "lodash";
const checkPositiveNumber = e => {  
  console.log(+e.target.value > 0);  
};

const numInput = document.querySelector("input[type=number]");  
numInput.addEventListener("input", _.debounce(checkPositiveNumber, 600));

The code above has the checkPositiveNumber function that checks if the value entered is positive. Then we use the debounce method, which takes the function and the delay before calling it in milliseconds.

The function returned by debouce has the same parameters and content as the original function, except that it’s delayed by the given number of milliseconds before calling it.

_.get

The get method lets us access the properties of an object in a safe way. That is, even if the path to the properties doesn’t exist, it will return undefined or a default value instead of crashing the program.

For example, given the following object:

const obj = {  
  foo: {  
    bar: { baz: { a: 3 } },  
    foo: { b: 2 },  
    baz: [1, 2, 3]  
  }  
};

We can access obj's property as follows:

const result = _.get(obj, "foo.bar.baz.a", 1);

The first argument is the object we want to access a property’s value. The second is the path to the property. The last argument is the default value.

We should get 3 for result.

On the other hand, if the path doesn’t exist or it’s undefined, then we get undefined or a default value returned.

For example, if we have:

const result = _.get(obj, "foo.bar.a", 1);

Then we get 1 for result.

If we don’t specify a default value as follows:

const result = _.get(obj, "foo.bar.a");

Then we get undefined.

There’s no way to safely get the value of a deeply nested property until the optional chaining operator becomes mainstream.

_.set

There’s also a set method to assign a value to a property of an object. For example, given the same object we had before:

const obj = {  
  foo: {  
    bar: { baz: { a: 3 } },  
    foo: { b: 2 },  
    baz: [1, 2, 3]  
  }  
};

We can set a value to a property by writing:

_.set(obj, "foo.bar.a", 1);

The obj object is changed in place. As we can see, it can set values for properties that don’t exist yet. The original object didn’t have foo.bar.a and it added it automatically set the value to 1.

So we get:

{  
  "foo": {  
    "bar": {  
      "baz": {  
        "a": 3  
      },  
      "a": 1  
    },  
    "foo": {  
      "b": 2  
    },  
    "baz": [  
      1,  
      2,  
      3  
    ]  
  }  
}

It even works if the nested object doesn’t exist, so if we write:

_.set(obj, "foo.foofoo.bar.a.b", 1);

We get:

{  
  "foo": {  
    "bar": {  
      "baz": {  
        "a": 3  
      }  
    },  
    "foo": {  
      "b": 2  
    },  
    "baz": [  
      1,  
      2,  
      3  
    ],  
    "foofoo": {  
      "bar": {  
        "a": {  
          "b": 1  
        }  
      }  
    }  
  }  
}

_.deburr

To remove accents from characters with strings, we can use the deburr method. It takes in a string and returns a new string with the characters that have accents replaced with the ones that don’t have them.

For example, if we have “S’il vous plaît”:

const result = _.deburr("S'il vous plaît");

Then we get that result is "S’il vous plait" . The ‘i’ no longer has the accent.

_.keyBy

The keyBy method takes an array and the property name and returns an object with the value of the property as the keys of the object.

For example, if we have the following array:

const people = [  
  { name: "Joe", age: 20 },  
  { name: "Jane", age: 18 },  
  { name: "Mary", age: 20 }  
];

Then we can use keyBy as follows:

const results = _.keyBy(people, "name");

Then results would have the following:

{  
  "Joe": {  
    "name": "Joe",  
    "age": 20  
  },  
  "Jane": {  
    "name": "Jane",  
    "age": 18  
  },  
  "Mary": {  
    "name": "Mary",  
    "age": 20  
  }  
}

Then we get the object with name 'Joe' by writing:

results['Joe']

There’s no way to do this easily with plain JavaScript without writing multiple lines of code.

Conclusion

Lodash has many useful functions that don’t have an equivalent that are as easy to use as these methods.

There’s the times method to call a function multiple times in one line. The debounce function returns a new function with the same signature and code as the original but it’s delayed by the given amount of milliseconds before it’s called.

For accessing and setting object properties and values safely, there are the get and set methods that don’t crash when we access property paths that don’t exist or has value undefined.

Then there’s the deburr method to replace accented characters with the non-accented versions of these characters.

Finally, there’s keyBy method to get massage an array into an object that has the given property’s value of each entry as the keys and the entry with the given property’s name’s values as the value of those keys.

Discussion (16)

Collapse
lexlohr profile image
Alex Lohr • Edited on

To do the same with ES methods, just the first two examples:

_.times

// import * as _ from "lodash";
const getRandomInteger = () => Math.round(Math.random() * 100);  
// let result = _.times(5, getRandomInteger);  
const result = Array(5).fill().map(getRandomInteger);  
console.log(result);

_.debounce

//import * as _ from "lodash";
let checkPositiveNumberTimeout;
const checkPositiveNumber = e => {
  clearTimeout(checkPositiveNumberTimeout);
  checkPositiveNumberTimeout = setTimeout(() => console.log(+e.target.value > 0), 600);
};

const numInput = document.querySelector("input[type=number]");  
// numInput.addEventListener("input", _.debounce(checkPositiveNumber, 600));
numInput.addEventListener("input", checkPositiveNumber);

These two example illustrate well that some things are easier to achieve without lodash than others. For me, the rule of thumb is: if I use a lodash method, I use it separately (never import all of it, and if possible, use the es version that can be tree-shaked!) and make sure I reuse it throughout the code so I actually get something in return.

In a surprisingly lot of cases I have seen, the use of utilities like lodash means outsourcing the deeper understanding of a problem to a library. While that can help speed up development in the short term, it might become a convenient habit, so much that one is tempted to overcomplicate the issue at hand in order to use these utilities when it could have been far simpler solved with native methods.

That does not mean you should not use lodash, but you should be conscientious about the implications.

Collapse
patarapolw profile image
Pacharapol Withayasakpunt • Edited on

but you should be conscientious about the implications.

This.

It makes me unsure if I should use lodash.clonedeep or lodash.merge... If I write it myself, I can be sure of the implications.

Also, lodash is too magical and too widely accepting in some methods, such as lodash.filter. (I saw a lot in lowdb.)

Collapse
lexlohr profile image
Alex Lohr

A very nice example is _.isEmpty(): in most cases, you are testing if an object has no enumerable keys. You'll rarely need to check at the same time if the length of an array or a string is zero, but lodash will (needlessly in the stated case) check the input for all available types.

Thread Thread
aumayeung profile image
John Au-Yeung Author

This is one of the ones that we can implement ourselves without much effort.

Collapse
aumayeung profile image
John Au-Yeung Author

I think the array's built-in filter method is pretty good so I never want to use the Lodash version.

Collapse
ionline247 profile image
Matthew Bramer

The problem with all the examples is

import * as _ from "lodash";

That'll pull in the entire lib. At this point, if you're looking for a utility, it should be very selective, much like your article suggests. That means your npm install should only pull in that utility, not the whole library.

Collapse
aumayeung profile image
John Au-Yeung Author

You're right. Just import the methods you need to use. That's more efficient.

Collapse
mateiadrielrafael profile image
Matei Adriel

Someone in another component has already shown how to implement times and debounce.Get can be achived with optional chaining, so heres how to do keyBy:

const keyBy = (arr, key) => Obhect.fromEntries(arr.map(o => [o[key], o]))

...and with types:

const keyBy = <T extends object, K extends keyof T>(arr: T[], key: K): Record<T[K] extends (string | number) ? T[K] : never, T>

(Have't tested this since I'm on mobile rigut now, but you yet the idea)

Conclusion

There’s no way to do this easily with plain JavaScript without writing multiple lines of code

Since its doable in 1 line your statemet kinda false

Collapse
patarapolw profile image
Pacharapol Withayasakpunt

To avoid Object.fromEntries, there is .reduce.

arr.map(o => [o[key], o]).reduce((prev, [k, v]) => { ...prev, [k]: v }, {})

Actually, with map, filter, reduce -- you can do a lot.

It is impossible to say that you cannot do something in one-liner JavaScript, because minified JS is also a one liner.

Collapse
aumayeung profile image
John Au-Yeung Author

I agree. I think there're things like differenceWith in Lodash that are harder to implement by ourselves and not available in the standard library.

Collapse
ajkachnic profile image
Andrew

Can’t you now using optional chaining instead of _get()

let obj = {
  person: {
    name: "Something"
  }
}
const name = obj?.person?.name
Collapse
aumayeung profile image
John Au-Yeung Author

I'm not sure if it's finalized yet, but hopefully, it will be soon so that we don't need Lodash for it.

Collapse
jlave_dev profile image
James Lave

Thanks for the post, John! I'll admit, Lodash always felt "dirty" to me, especially since so many of its methods are now part of the ECMA standard or can be implemented in a few lines of native JavaScript. But the ones you highlighted are still very useful.

Collapse
aumayeung profile image
John Au-Yeung Author

It's useful if you don't want to create your own function to do the things it does.

Collapse
jharteaga profile image
Jose Arteaga

Very helpful! Thanks for sharing this worthy information.

Collapse
aumayeung profile image
John Au-Yeung Author

Thanks for reading