Migrate: lodash

filter

Use isTruthy as the predicate.

// Lodash
compact(DATA);

// Remeda
filter(DATA, isTruthy);

difference

View source on GitHub
difference

In Remeda difference treats the inputs as multisets/bags which respect item duplication, whereas in Lodash all matching values are filtered out. This only matters for cases where both arrays might have duplicate values. Use filter, isNot, and isIncludedIn instead to compose the same logic.

No duplicates

// Lodash
difference([2, 1], [2, 3]);

// Remeda
difference([2, 1], [2, 3]);

With duplicates

// Lodash
difference([1, 1, 2, 2], [1]);

// Remeda
filter([1, 1, 2, 2], isNot(isIncludedIn([1])));
flat

Either use with no params, or with a depth of 1.

// Lodash
flatten(DATA);

// Remeda
flat(DATA);
flat(DATA, 1 /* depth */);

flattenDeep

View source on GitHub
flat

Unlike flatten in Lodash, the Remeda flat function is always bound by the depth param. To replicate the Lodash behavior use a high, const value. Infinity and Number.MAX_INTEGER are not consts and would result in inefficient typing.

// Lodash
flattenDeep(DATA);

// Remeda
flat(DATA, 100); // ✅
flat(DATA, Infinity); // ❌

intersection

View source on GitHub
intersection

In Remeda intersection treats the inputs as multisets/bags which respect item duplication, whereas in Lodash the result is always de-dupped. This only matters for cases where both arrays might have duplicate values. To achieve the same output pass the result to unique.

No duplicates

// Lodash
intersection([2, 1], [2, 3]);

// Remeda
intersection([2, 1], [2, 3]);

With duplicates

// Lodash
intersection([1, 1], [1, 1]);

// Remeda
unique(intersection([1, 1], [1, 1]));

Not provided by Remeda.

  • For index 0 use first.
  • For index -1 use last.
  • For arbitrary non-negative indices use the native JS data[n].
  • Or use Array.prototype.at for any index.
// Lodash
nth(DATA);
nth(DATA, 0);

// Remeda
first(DATA);

// Lodash
nth(DATA, 1);

// Native
DATA[1];
DATA.at(1);

// Lodash
nth(DATA, -1);

// Remeda
last(DATA);

// Lodash
nth(DATA, -2);

// Native
DATA.at(-2);
reverse

Lodash mutates the input but in Remeda a new array is returned instead.

// Lodash
const DATA = [1, 2, 3];
reverse(DATA);

// Remeda
let DATA = [1, 2, 3];
DATA = reverse(DATA);

sortedUniq

View source on GitHub
unique

Not provided by Remeda.

There is no equivalent function in Remeda because both unique and sortedUniq run at O(n) time complexity. In space complexity (the amount of memory needed to compute them), unique is O(k) where k is the number of unique values in the array, while sortedUniq is O(1). In most real-life use cases, the two implementations would have near-identical performance. We highly recommend always using unique unless you know that the difference matters.

If you still need this algorithm, use filter with the following predicate:

// Lodash
sortedUniq(DATA);

// Remeda
unique(DATA);
filter(DATA, (item, index, array) => index === 0 || item !== array[index - 1]);

Not provided by Remeda.

Compose zip and fromEntries:

// Lodash
zipObject(keys, values);

// Remeda
fromEntries(zip(keys, values));

countBy

View source on GitHub

Not provided by Remeda.

This function could be recreated in Remeda via a composition of groupBy, mapValues, and length. We acknowledge that this is less efficient than a singular implementation; if you need this function please open an issue at the Remeda GitHub project to get it added.

// Lodash
countBy(DATA, groupingFunction);

// Remeda
mapValues(groupBy(DATA, groupingFunction), length());

// Or as a pipe
pipe(DATA, groupBy(groupingFunction), mapValues(length()));

every

View source on GitHub

flatMapDeep

View source on GitHub

Not provided by Remeda.

This function could be recreated in Remeda via a composition of map, and flat with a large, const depth parameter.

// Lodash
flatMapDeep(DATA, mappingFunction);

// Remeda
flat(map(DATA, mappingFunction), 10 /* depth */);

// Or as a pipe
pipe(DATA, map(mappingFunction), flat(10 /* depth */));

flatMapDepth

View source on GitHub

Not provided by Remeda.

This function could be recreated in Remeda via map, flatMap, or flat (or a combination of them, depending on the parameters used).

// Lodash
flatMapDepth(DATA);

// Remeda
flat(DATA);
// Lodash
flatMapDepth(DATA, mappingFunction);

// Remeda
flatMap(DATA, mappingFunction);
// Lodash
flatMapDepth(DATA, mappingFunction, depth);

// Remeda
flat(map(DATA, mappingFunction), depth);

// Or as a pipe
pipe(DATA, map(mappingFunction), flat(depth));

forEach

View source on GitHub
forEach
  • In Lodash forEach first checks if the data argument is array-like (by looking for a length prop). In Remeda the function only accepts arrays and calling it with an object would result in a TypeScript error. Use forEachObj instead for those cases.
  • In Lodash forEach stops iterating early when the callback returns false. In Remeda the callback cannot return a value (it must return void) and this feature isn’t available. You can build a pipe that would replicate this logic.

Array

// Lodash
forEach([1, 2, 3], (item) => console.log(item));

// Remeda
forEach([1, 2, 3], (item) => {
  console.log(item);
});

Object

// Lodash
forEach({ a: 1, b: 2, c: 3 }, (value) => console.log(value));

// Remeda
forEachObj({ a: 1, b: 2, c: 3 }, (value) => {
  console.log(value);
});

Early exit

// Lodash
forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));

// Remeda
// ❌ You can't return a value in a forEach callback in Remeda:
forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));

// ✅ Instead you can rely on lazy evaluation of pipes:
pipe(
  [1, 2, 3],
  takeWhile((item) => item !== 2),
  forEach((item) => {
    console.log(item);
  }),
);

forEachRight

View source on GitHub

Not provided by Remeda.

  • This function could be replicated using a native JS for loop.
  • If the order of iteration doesn’t matter, you can use forEach for arrays, or forEachObj for objects.
  • The order in which object properties are iterated over is well-defined, but might not be the order you expect (MDN); nevertheless, entries maintains the same order, and could be used to replicate forEachRight.
  • If the native solution doesn’t suffice please open an issue at Remeda’s GitHub project so we can learn about your use-case.

Arrays

const DATA = [1, 2, 3];

// Lodash
forEachRight(DATA, (item) => (item === 2 ? false : console.log(item)));

// Native
for (const index = DATA.length - 1; index >= 0; index--) {
  const item = DATA[item]!;
  if (item === 2) {
    break;
  }

  console.log(item);
}

Objects

const DATA = { a: 1, b: 2, c: 3 };

// Lodash
forEachRight(DATA, (value) => (value === 2 ? false : console.log(value)));

// Native
const keys = Object.keys(DATA);
for (const index = keys.length - 1; index >= 0; index--) {
  const value = DATA[key]!;
  if (value === 2) {
    break;
  }

  console.log(value);
}

includes

View source on GitHub
isIncludedIn
  • In Lodash includes also works on strings and objects; In Remeda only arrays are supported. For objects, use values first. For strings prefer the native JS String.prototype.includes.
  • The 3rd optional parameter fromIndex isn’t supported in Remeda. You can replicate it’s usage with drop (for non-negative indices), or takeLast (for non-positive indices).
  • The order of the parameters is flipped in Remeda, the item is first, and then the array.

Arrays

// Lodash
includes([1, 2, 3], 1);

// Remeda
isIncludedIn(1, [1, 2, 3]);

Objects

// Lodash
includes({ a: 1, b: 2 }, 1);

// Remeda
isIncludedIn(1, values({ a: 1, b: 2 }));

Strings

const DATA = "Hello, World!";

// Lodash
includes(DATA, "lo");

// Native
DATA.includes("lo");

fromIndex: non-negative

// Lodash
includes([1, 2, 3], 1, 2);

// Remeda
isIncludedIn(1, drop([1, 2, 3], 2));

fromIndex: non-positive

// Lodash
includes([1, 2, 3], 1, -2);

// Remeda
isIncludedIn(1, takeLast([1, 2, 3], 2));

invokeMap

View source on GitHub
map
  • map can be used instead of invokeMap by using a callback functions instead of (string) function names.
  • If you need to invoke functions dynamically via a provided function name, narrow the name first (so you can handle errors explicitly), and then use the name to access the object.

No arguments

// Lodash
invokeMap(
  [
    [5, 1, 7],
    [3, 2, 1],
  ],
  "sort",
);

// Remeda
map(
  [
    [5, 1, 7],
    [3, 2, 1],
  ],
  (array) => array.sort(),
);

With arguments

// Lodash
invokeMap([123, 456], String.prototype.split, "");

// Remeda
map([123, 456], (num) => num.toString().split(""));

Dynamic

const DATA = [
  { foo: (x: number) => x + 1, bar: (x: number) => x - 1 },
  { foo: (x: number) => x * 2, bar: (x: number) => x / 2 },
] as const;

// Lodash
invokeMap(DATA, funcName, 3);

// Remeda
if (funcName === "foo" || funcName === "bar") {
  map(DATA, ({ [funcName]: func }) => func(3));
} else {
  // Error!
}

// Or
map(DATA, (obj) => {
  if (funcName === "foo" || funcName === "bar") {
    return obj[funcName](3);
  } else {
    // Error
  }
});

orderBy

View source on GitHub
sortBy

Unlike in Lodash, in Remeda the sort criteria can only be a callback (and not a prop name), it is paired with the order modifier (“asc”/“desc”, if needed), and it’s provided as a variadic argument and not two array arguments.

const DATA = [
  { user: "fred", age: 48 },
  { user: "barney", age: 34 },
  { user: "fred", age: 40 },
  { user: "barney", age: 36 },
];

// Lodash
orderBy(DATA, ["user", "age"], ["asc", "desc"]);

// Remeda
sortBy(DATA, prop("user"), [prop("age"), "desc"]);

reduceRight

View source on GitHub
reduce

Use reverse on the input.

// Lodash
reduceRight(DATA, reducer, accumulator);

// Remeda
reduce(reverse(DATA), reducer, accumulator);

// Or with a pipe
pipe(DATA, reverse(), reduce(reducer, accumulator));

reject

View source on GitHub
filter

Wrap the callback with isNot.

// Lodash
reject(DATA, predicate);

// Remeda
filter(DATA, isNot(predicate));

sample

View source on GitHub
sample
  • The sample function in Remeda takes a required second parameter of the size of the sample. Use 1.
  • The Remeda function returns an array; to extract the value either use native destructuring or index access, or use only.
  • For objects, first call values.
  • If you are using sample in an extremely hot path where you need the most efficient implementation possible, prefer a native implementation instead.

Simple

// Lodash
sample(DATA);

// Remeda
only(sample(DATA, 1 /* size */));

// or with a pipe
pipe(DATA, sample(1 /* size */), only());

// Or with native accessors
sample(DATA, 1 /* size */)[0];
sample(DATA, 1 /* size */).at(0)!;
const [result] = sample(DATA, 1 /* size */);

Native

// Lodash
sample(DATA);

// Native
DATA[Math.floor(Math.random() * DATA.length)]!;
DATA.at(Math.floor(Math.random() * DATA.length))!;

sampleSize

View source on GitHub
sample
  • Unlike Lodash, In Remeda the sample function returns items in the same order they appear in the input. Use the shuffle function if you also need to randomize their order.
  • The second parameter to sampleSize in Lodash is optional, and defaults to 1 when not provided. In Remeda this is a required param.
// Lodash
sampleSize(DATA);

// Remeda
sample(DATA, 1);
// Lodash
sampleSize(DATA, 2);

// Remeda
// ❌ The result isn't shuffled!
sample(DATA, 2);

// ✅ Add `shuffle` after `sample` (if order is important).
shuffle(sample(DATA, 2));

// Or with a pipe
pipe(DATA, sample(2), shuffle());

sortBy

View source on GitHub
sortBy

The sortBy function in Remeda does not support defining the sort criteria using prop names, only callback functions. Also, the sort criteria is provided as a variadic argument of sort criteria, and not a single array argument.

const DATA = [
  { user: "fred", age: 48 },
  { user: "barney", age: 34 },
  { user: "fred", age: 40 },
  { user: "barney", age: 36 },
];

// Lodash
sortBy(DATA, [
  function (o) {
    return o.user;
  },
]);
sortBy(DATA, ["user", "age"]);

// Remeda
sortBy(DATA, prop("user"));
sortBy(DATA, prop("user"), prop("age"));

partial

View source on GitHub
partialBind

The placeholder argument _ is not supported. Some cases can be written as partialLastBind; otherwise, use an arrow function.

function greet(greeting, firstName, lastName) {
  return greeting + " " + firstName + " " + lastName;
}

// Lodash
_.partial(greet, _, "john", "doe");
_.partial(greet, "hi", _, "doe");

// Remeda
partialLastBind(greet, "john", "doe");
// (not supported)

// Native
(greeting) => greet(greeting, "john", "doe");
(firstName) => greet("hi", firstName, "doe");

partialRight

View source on GitHub
partialLastBind

The placeholder argument _ is not supported. Some cases can be written as partialBind; otherwise, use an arrow function.

function greet(greeting, firstName, lastName) {
  return greeting + " " + firstName + " " + lastName;
}

// Lodash
_.partialRight(greet, "hi", "john", _);
_.partialRight(greet, "hi", _, "doe");

// Remeda
partialBind(greet, ["hi", "john"]);
// (not supported)

// Native
(lastName) => greet("hi", "john", lastName);
(firstName) => greet("hi", firstName, "doe");
firstBy

Use identity as the criteria and "desc" as the direction.

// Lodash
max(DATA);

// Remeda
firstBy(DATA, [identity(), "desc"]);
firstBy

Use "desc" as the direction.

// Lodash
maxBy(DATA, criteria);

// Remeda
firstBy(DATA, [criteria, "desc"]);
firstBy

Use identity as the criteria.

// Lodash
min(DATA);

// Remeda
firstBy(DATA, identity());
randomInteger
  • When Lodash’s random function is called with a non-integer param it returns any number, not just integers (e.g. 2.5)! Remeda’s randomInteger function always returns integers, effectively rounding the parameters to fit the range of possible integers.
  • If you want to generate any number in the range (and not just integers) see the solutions provided in “You don’t need Lodash”.
  • Lodash’s random parameters are optional. In Remeda all parameters are required.

Two integer params

// Lodash
random(1, 10);

// Remeda
randomInteger(1, 10);

Single integer param

// Lodash
random(10);

// Remeda
randomInteger(0, 10);

No params

// Lodash
random();

// Remeda
randomInteger(0, 1);

Not supported: floating-point numbers

random(1.5, 3.5);
random(1.5);
random(10, true);
random(5, 10, true);
random(true);
merge
  • In Lodash the merge is done in-place, on the first argument to the function. In Remeda a new object is always returned, and none of the input objects are mutated.
  • In Lodash assign can be used to merge any number of objects by accepting a variadic argument list. In Remeda, if you are merging only 2 objects use merge, but if you need to merge more than 2 objects use mergeAll instead, which takes an Array of objects.
// Lodash
const DATA = { a: 1, b: 2 };
assign(DATA, b);
assign(DATA, b, c, d, e);

// Remeda
let DATA = { a: 1, b: 2 };
DATA = merge(DATA, b);
DATA = mergeAll([DATA, b, c, d, e]);
pathOr
  • In Lodash the get function supports two ways of defining the path parameter: a string representation of the path (similar to XPath: e.g. a.b[0].c), and an array representation of the path (e.g. ['a', 'b', 0, 'c']). In Remeda only the array representation is accepted. Use the helper function stringToPath to translate string paths to array paths.
  • In order to provide good types, the Remeda pathOr function is limited to paths of length 3 or less. Longer paths are not supported.
  • prop is a simplified version of pathOr for paths of length 1 and undefined as the default (fallback) value.
const DATA = { a: [{ b: 123 }] };

// Lodash
get(DATA, "a");
get(DATA, ["a", 0, "b"], 456);
get(DATA, "a[0].b", 456);

// Remeda
prop(DATA, "a");
pathOr(DATA, ["a", 0, "b"], 456);
pathOr(DATA, stringToPath("a[0].b"), 456);
setPath
  • In Lodash the set function supports two ways of defining the path parameter: a string representation of the path (similar to XPath: e.g. a.b[0].c), and an array representation of the path (e.g. ['a', 'b', 0, 'c']). In Remeda only the array representation is accepted. Use the helper function stringToPath to translate string paths to array paths.
  • Unlike the Lodash set function, In Remeda the provided value must match the type of the prop at that path, the function does not support creating “sparse” objects.
  • For better type-safety, Remeda offers two additional functions to handle paths of length 1. Use set to update an existing prop in an object (best type-safety and with IDE type-ahead), and addProp to add a new prop to the object (or to override its type)
  • In Lodash set mutates the input object. In Remeda a new object is returned instead. The input object is never mutated.
let data = { a: "hello", deep: [{ z: true }] };

// Lodash
set(data, "a", "world");
set(data, ["a"], "foo");

set(data, "a", 123);
set(data, ["a"], 456);
set(data, "b", 123);
set(data, ["b"], 456);

set(data, "deep[0].z", false);
set(data, ["deep", 0, "z"], false);

// ❌ Not supported:
set(data, "deep[0].newProp", 123);
set(data, ["deep", 0, "newProp"], 123);

// Remeda
data = set(data, "a", "world");
data = set(data, "a", "foo");

data = addProp(data, "a", 123);
data = addProp(data, "a", 456);
data = addProp(data, "b", 123);
data = addProp(data, "b", 456);

data = setPath(data, stringToPath("deep[0].z"), false);
data = setPath(data, ["deep", 0, "z"], false);

camelCase

View source on GitHub
toCamelCase
  • Camel-casing relies heavily on how “words” are broken up. Remeda uses the same logic that type-fest uses. This might work slightly different from Lodash.
  • Remeda offers an optional flag for toCamelCase that changes how consecutive uppercase letters are handled. This flag is turned on by default, but the behavior is more similar to Lodash when it is turned off.
// Lodash
camelCase("HasHTML"); // "hasHtml"

// Remeda
toCamelCase("HasHTML"); // "hasHTML";

toCamelCase("HasHTML", { preserveConsecutiveUppercase: true }); // "hasHTML";
toCamelCase("HasHTML", { preserveConsecutiveUppercase: false }); // "hasHtml";

Not provided by Remeda.

Use the native JS String.prototype.split with the regex /\s+/u instead.

// Lodash
words(str);

// Native
str.split(/\s+/u);
when
  • Use Remeda’s [when] with isNullish as the predicate and the fallback value wrapped with constant.
  • For defaulting NaN values use the built-in Number.isNaN instead.
  • For both you’d need to construct a type-guard manually.

Nullish

// Lodash
defaultTo(DATA, 10);

// Remeda
when(DATA, isNullish, constant(10));

NaN

// Lodash
defaultTo(DATA, 10);

// Remeda
when(DATA, Number.isNaN, constant(10));

Both

// Lodash
defaultTo(DATA, 10);

// Remeda
when(
  DATA,
  (x) => x === undefined || x === null || Number.isNaN(x),
  constant(10),
);
identity

In Remeda the identity function is a factory that creates an identity function; it needs to be called to be used.

// Lodash
map(identity);

// Remeda
map(identity());
doNothing
  • If a return value is needed, use constant with undefined.
  • Otherwise, use doNothing (which returns void).
// Lodash
noop;

// Remeda
constant(undefined);
doNothing();