DEV Community

You Might Not Need Lodash

Antonin J. (they/them) on March 15, 2018

You can check out more of my tutorials and articles on my main blog. Enjoy the article! While Lodash may have become a staple in any JavaScript de...
Collapse
 
gsonderby profile image
Gert Sønderby • Edited

Extremely quick and dirty homebrew of a flatten function:

const flatten = array => array.reduce(
  (flatArray, item) => {
    if (Array.isArray(item)) {
      return flatArray.concat(flatten(item));
    } else {
      return flatArray.concat([item]);
    }
  },
  []
);

EDITED: Return a fresh array instead of modifying flatArray. Clearer, less prone to nitpickery.

Collapse
 
masaeedu profile image
Asad Saeeduddin

It's a little confusing to simultaneously use reduce and push. Use reduce when you're actually computing a new value, use a for of loop when you're mutating things in a loop.

// Use this
const flattened = as => as.reduce((p, c) => [...p, ...c], [])

// Or this
const flattened = []
for (const item of as) {
  flattened.push(...item)
}
Collapse
 
gsonderby profile image
Gert Sønderby

Your first example chokes on [1,[2,3,[4]],5,[6,7]], throwing an error. Worse, on ['yo',['dawg','I',['herd']],'you',['like','arrays']] it breaks in a number of interesting ways, spreading some strings, failing to spread some arrays.

Thread Thread
 
masaeedu profile image
Asad Saeeduddin

It is an implementation of the join function for arrays, and works with arrays of arrays, not arrays of mixed objects.

If you need it to work with mixed objects you'll need a recursive call (or ap+pure):

const flattened = as => as.reduce((p, c) => [...p, ...(Array.isArray(c) ? flattened(c) : [c])], [])

Anyway, the point is to avoid mutation in the reduce, whatever it is you may be implementing.

Thread Thread
 
gsonderby profile image
Gert Sønderby

At this point it honestly seems like you're just trying to score points against me, in some way. I am disinterested in continuing that.

Thread Thread
 
masaeedu profile image
Asad Saeeduddin

Sorry to hear you feel that way. I was just suggesting that using mutation in reduce is confusing, didn't mean for it to turn into this long-running back and forth.

Collapse
 
gsonderby profile image
Gert Sønderby

The array I am pushing to is the accumulator of the reduce call, though. I'm pushing the elements of each array found, once flattened.

It works like a classic head/tail recursion, if an element is an array it first gets flattened itself, then it's elements are pushed onto the accumulator.

Thread Thread
 
masaeedu profile image
Asad Saeeduddin

That's fine, but as I said, it's confusing to use reduce and mutation simultaneously. You're passing [] as a seed value, so the only thing that got mutated was that seed array. If you'd used array.reduce((flatArray, item) => ...) without the seed value (which would be fine but for the mutation), it would end up filling the first item in the input array you were passed.

In general it's easier on the fallible human developer to make mutation look like mutation and pure computation look like pure computation.

Thread Thread
 
gsonderby profile image
Gert Sønderby

The only way it would matter here is if you somehow changed the function to make flatArray available while the function was running. I'm not even sure how you'd do that. But I do want to point your attention to the words, used above: "quick and dirty"...

Collapse
 
antjanus profile image
Antonin J. (they/them) • Edited

Yeah, I was thinking about posting code snippets that replace Lodash but then I quickly realized that Lodash is SUPER lightweight to begin with and can do this stuff much better than any snippet I can write. Plus docs/tests/code coverage/edge case solutions are part of using Lodash.

You can check it out here and for convenience, here's a copy:

    function baseFlatten(array, depth, predicate, isStrict, result) {
      var index = -1,
          length = array.length;

      predicate || (predicate = isFlattenable);
      result || (result = []);

      while (++index < length) {
        var value = array[index];
        if (depth > 0 && predicate(value)) {
          if (depth > 1) {
            // Recursively flatten arrays (susceptible to call stack limits).
            baseFlatten(value, depth - 1, predicate, isStrict, result);
          } else {
            arrayPush(result, value);
          }
        } else if (!isStrict) {
          result[result.length] = value;
        }
      }
      return result;
}
Collapse
 
gsonderby profile image
Gert Sønderby

I admit I hesitate to call that bit lightweight, at least in terms of reading and understanding it.😀

Your point re. testing and edge cases and so forth is well seen. My example was mainly to give an alternative where a native function does not currently exist.

Thread Thread
 
antjanus profile image
Antonin J. (they/them) • Edited

Lightweight in size! Especially if you import only what you need. eg:

// import directly what you need
import clone from 'lodash/clone';

// or if you have tree shaking
import { clone } from 'lodash';

// or if you use the lodash babel plugin
import _ from 'lodash';

//and then use it!

Reading it isn't too bad but their looping is bizarre to me. It's done for memory/speed use but counting down from a number in a while loop instead of doing a for loop baffles me but I understand they have reasons for it.

Collapse
 
mlg profile image
m-lg

Quick way to flatten 1 deep
const feed = [].concat(...sourceArray)

Collapse
 
nicolasnarvaez profile image
Nicolás Narváez

i do the same ;)

Collapse
 
antjanus profile image
Antonin J. (they/them)

wow. That's awesome! It took me a few to figure out how this works.

Collapse
 
paulasantamaria profile image
Paula Santamaría

Great article! Whenever I feel like I need to add Lodash to a new project I try to solve my immediate needs with plain Javascript first. Turns out most of the times that's enough.
It's an awesome library, but I like to keep my projects as simple as possible.

Collapse
 
stevealee profile image
SteveALee

You can also use mutlple sources in the RHS of a spead expression to get a merge

Collapse
 
antjanus profile image
Antonin J. (they/them) • Edited

do you mean like this?

const mergedObj = { ...obj1, ...obj2 };

My only issue with it is that it doesn't recursively merge. It's fine for flat objects. I use this pattern pretty often at work, sometimes in conjunction with lodash.

For instance, I use Objection for ORM and it has "computed properties" that need to be accessed in order to get the value, they're not serialized when doing toJSON so I often resort to:

res.send({ ...someModel, ..._.pick(someModel, ['computedProperty1', 'computedProp2']);

EDIT Totally forgot, this is a pretty common pattern in Redux!

Collapse
 
stevealee profile image
SteveALee

Completely agree. Its shallow like Object.assign. I was just say is an alternative to it really.

Collapse
 
whiteadi profile image
Adrian Albu