Migrate: lodash
Array
difference
-
Remeda’s
difference
treats the inputs as multisets/bags which respect item duplication, whereas in Lodash all matching values are filtered out. This means Remeda removes only one copy of each matching item, while Lodash removes all copies. -
When the input arrays might contain duplicates and you want to filter all values use a composition of
filter
,isNot
, andisIncludedIn
to replicate the Lodash semantics. -
Lodash’s
difference
accepts multiple values arrays as separate arguments and flattens them, while Remeda’sdifference
takes a single values array. When migrating these arrays need to be flattened or spread into a single array manually. -
Lodash supports calling
difference
trivially, with no exclusion array at all which results in a shallow clone of the input array. In Remeda the exclusion array is required. -
Lodash accepts
null
andundefined
values for the array (and treats them as an empty array). In Remeda this nullish value needs to be handled explicitly either by skipping the call todifference
, or by coalescing the input to an empty array.
Without duplicate values (unique)
// Lodash
_.difference(uniqueData, uniqueValues);
// Remeda
difference(uniqueData, uniqueValues);
With duplicate values
// Lodash
_.difference(dataWithDuplicates, uniqueValues);
_.difference(uniqueData, valuesWithDuplicates);
_.difference(dataWithDuplicates, valuesWithDuplicates);
// Remeda
filter(dataWithDuplicates, isNot(isIncludedIn(uniqueValues)));
filter(uniqueData, isNot(isIncludedIn(valuesWithDuplicates)));
filter(dataWithDuplicates, isNot(isIncludedIn(valuesWithDuplicates)));
// valuesWithDuplicates doesn't need to be deduped when used inside
// `isIncludedIn`, but can be for efficiency if needed via `unique`
filter(dataWithDuplicates, isNot(isIncludedIn(unique(valuesWithDuplicates))));
Multiple exclusion arrays
// Lodash
_.difference(DATA, a, b, c);
// Remeda
difference(DATA, [...a, ...b, ...c]);
Missing 2nd (exclusions) parameter
// Lodash
_.difference(DATA);
// Remeda
difference(DATA, []);
// Or directly via Native JS:
[...DATA];
Nullish inputs
// Lodash
_.difference(DATA, values);
// Remeda
isNonNullish(DATA) ? difference(DATA, values) : [];
// Or
difference(DATA ?? [], values);
filter
Not provided by Remeda.
-
Use a composition based on
filter
,isNot
isIncludedIn
, andmap
to recreate the logic fordifferenceBy
. -
Additionally, when the iteratee parameter is defined as a property name, use
prop
as the iteratee function instead. -
Lodash accepts
null
andundefined
values for the array (and treats them as an empty array). In Remeda this nullish value needs to be handled explicitly either by skipping the call todifferenceBy
, or by coalescing the input to an empty array. -
When the
iteratee
parameter is not provided to the LodashdifferenceBy
function (or is provided asundefined
) it behaves like a call todifference
.
Function iteratee
// Lodash
_.differenceBy(DATA, values, iteratee);
// Remeda
filter(DATA, piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))));
// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
DATA,
piped(iteratee, isNot(isIncludedIn(unique(map(values, iteratee))))),
);
Property iteratee
// Lodash
_.differenceBy(DATA, values, "x");
// Remeda
filter(DATA, piped(prop("x"), isNot(isIncludedIn(map(values, prop("x"))))));
// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
DATA,
piped(prop("x"), isNot(isIncludedIn(unique(map(values, prop("x")))))),
);
Multiple exclusion arrays
// Lodash
_.differenceBy(DATA, a, b, c, iteratee);
// Remeda
filter(
DATA,
piped(iteratee, isNot(isIncludedIn(map([...a, ...b, ...c], iteratee)))),
);
// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
DATA,
piped(
iteratee,
isNot(isIncludedIn(unique(map([...a, ...b, ...c], iteratee)))),
),
);
Nullish inputs
// Lodash
_.differenceBy(DATA, values, iteratee);
// Remeda
isNonNullish(DATA)
? filter(DATA, piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))))
: [];
// Or
filter(DATA ?? [], piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))));
Missing iteratee function
// Lodash
_.differenceBy(DATA, values);
// Convert to Lodash's `difference` and then refer to the `difference` migration
// docs
_.difference(DATA, values);
differenceWith
-
Lodash’s
differenceWith
accepts a variadic list of values arrays which are then effectively flattened, but Remeda only accepts a single values array; Multiple arrays need to be spread manually instead. -
Lodash accepts
null
andundefined
values for the array (and treats them as an empty array). In Remeda this nullish value needs to be handled explicitly either by skipping the call todifferenceWith
, or by coalescing the input to an empty array. -
When the
comparator
parameter is not provided to the LodashdifferenceWith
function (or is provided asundefined
) it behaves like a call todifference
.
Single exclusion array
// Lodash
_.differenceWith(DATA, values, comparator);
// Remeda
differenceWith(DATA, values, comparator);
Multiple exclusion arrays
// Lodash
_.differenceWith(DATA, a, b, c, comparator);
// Remeda
differenceWith(DATA, [...a, ...b, ...c], comparator);
Nullish inputs
// Lodash
_.differenceWith(DATA, values, comparator);
// Remeda
isNonNullish(DATA) ? differenceWith(DATA, values, comparator) : [];
// Or
differenceWith(DATA ?? [], values, comparator);
Missing iteratee function
// Lodash
_.differenceWith(DATA, values);
// Convert to Lodash's `difference` and then refer to the `difference` migration
// docs
_.difference(DATA, values);
flat
Either use with no params, or with a depth of 1.
// Lodash
flatten(DATA);
// Remeda
flat(DATA);
flat(DATA, 1 /* depth */);
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
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
usefirst
. - For index
-1
uselast
. - 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);
Not provided by Remeda.
CAUTION: pull
mutates the input array in-place! All functions in Remeda
are pure and never mutate the
input. These mutations might introduce side-effects and implicit dependencies in
your codebase that need to be handled before migrating to Remeda!
-
pull
is equivalent to Lodash’sdifference
function. To migrate to Remeda first migrate calls to Lodash_.difference
and then use the migration docs fordifference
to complete the migration. IMPORTANT: The Remedadifference
function isn’t a drop-in replacement for the Lodash_.difference
function, we do not recommend migrating directly frompull
to Remeda’sdifference
. -
pull
takes a variadic array of items to remove;difference
take an explicit array instead. You will need to wrap your items in an array when migrating. -
If the mutability of the input array is desired, make sure the variable is assignable (e.g., using
let
instead ofconst
), and assign back the result ofdifference
back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer containing object with the updated array manually. -
If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling
pull
, that cloning is now redundant.
Exclusion Items
// pull
_.pull(DATA, a, b, c, ...additional);
// difference
_.difference(DATA, [a, b, c, ...additional]);
In-place mutation
// pull
const DATA = ["a", "b", "c", "d"];
_.pull(DATA, ...values);
// difference
let DATA = ["a", "b", "c", "d"];
DATA = _.difference(DATA, values);
Non-mutating usage
// pull
const pulled = _.pull([...DATA], ...values);
// difference
const pulled = _.difference(DATA, values);
Nested inside an object
// pull
const DATA = { a: ["a", "b", "c"], b: "hello, world" };
_.pull(DATA.a, ...values);
// difference
let DATA = { a: ["a", "b", "c"], b: "hello, world" };
DATA = { ...DATA, a: _.difference(DATA.a, values) };
Not provided by Remeda.
CAUTION: pullAll
mutates the input array in-place! All functions in
Remeda are pure and never
mutate the input. These mutations might introduce side-effects and implicit
dependencies in your codebase that need to be handled before migrating to
Remeda!
-
pullAll
is equivalent to Lodash’sdifference
function. To migrate to Remeda first migrate calls to Lodash_.difference
and then use the migration docs fordifference
to complete the migration. IMPORTANT: The Remedadifference
function isn’t a drop-in replacement for the Lodash_.difference
function, we do not recommend migrating directly frompullAll
to Remeda’sdifference
. -
If the mutability of the input array is desired, make sure the variable is assignable (e.g., using
let
instead ofconst
), and assign back the result ofdifference
back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually. -
If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling
pullAll
, that cloning is now redundant.
In-place mutation
// pullAll
const DATA = ["a", "b", "c", "d"];
_.pullAll(DATA, values);
// difference
let DATA = ["a", "b", "c", "d"];
DATA = _.difference(DATA, values);
Non-mutating usage
// pullAll
const pulled = _.pullAll([...DATA], values);
// difference
const pulled = _.difference(DATA, values);
Nested inside an object
// pullAll
const DATA = { a: ["a", "b", "c"], b: "hello, world" };
_.pullAll(DATA.a, values);
// difference
let DATA = { a: ["a", "b", "c"], b: "hello, world" };
DATA = { ...DATA, a: _.difference(DATA.a, values) };
Not provided by Remeda.
CAUTION: pullAllBy
mutates the input array in-place! All functions in
Remeda are pure and never
mutate the input. These mutations might introduce side-effects and implicit
dependencies in your codebase that need to be handled before migrating to
Remeda!
-
pullAllBy
is equivalent to Lodash’sdifferenceBy
function. To migrate to Remeda first migrate calls to Lodash_.differenceBy
and then use the migration docs fordifferenceBy
to complete the migration. -
If the mutability of the input array is desired, make sure the variable is assignable (e.g., using
let
instead ofconst
), and assign back the result ofdifferenceBy
back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually. -
If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling
pullAllBy
, that cloning is now redundant.
In-place mutation
// pullAllBy
const DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
_.pullAllBy(DATA, values, iteratee);
// differenceBy
let DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
DATA = _.differenceBy(DATA, values, iteratee);
Non-mutating usage
// pullAllBy
const pulled = _.pullAllBy([...DATA], values, iteratee);
// differenceBy
const pulled = _.differenceBy(DATA, values, iteratee);
Nested inside an object
// pullAllBy
const DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
_.pullAllBy(DATA.items, values, iteratee);
// differenceBy
let DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
DATA = { ...DATA, items: _.differenceBy(DATA.items, values, iteratee) };
Not provided by Remeda.
CAUTION: pullAllWith
mutates the input array in-place! All functions in
Remeda are pure and never
mutate the input. These mutations might introduce side-effects and implicit
dependencies in your codebase that need to be handled before migrating to
Remeda!
-
pullAllWith
is equivalent to Lodash’sdifferenceWith
function. To migrate to Remeda first migrate calls to Lodash_.differenceWith
and then use the migration docs fordifferenceWith
to complete the migration. IMPORTANT: The RemedadifferenceWith
function isn’t a drop-in replacement for the Lodash_.differenceWith
function, we do not recommend migrating directly frompullAllWith
to Remeda’sdifferenceWith
. -
If the mutability of the input array is desired, make sure the variable is assignable (e.g., using
let
instead ofconst
), and assign back the result ofdifferenceWith
back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually. -
If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling
pullAllWith
, that cloning is now redundant.
In-place mutation
// pullAllWith
const DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
_.pullAllWith(DATA, values, comparator);
// differenceWith
let DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
DATA = _.differenceWith(DATA, values, comparator);
Non-mutating usage
// pullAllWith
const pulled = _.pullAllWith([...DATA], values, comparator);
// differenceWith
const pulled = _.differenceWith(DATA, values, comparator);
Nested inside an object
// pullAllWith
const DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
_.pullAllWith(DATA.items, values, comparator);
// differenceWith
let DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
DATA = { ...DATA, items: _.differenceWith(DATA.items, values, comparator) };
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);
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.
-
without
is equivalent to Lodash’sdifference
function. To migrate to Remeda first migrate calls to Lodash_.difference
and then use the migration docs fordifference
to complete the migration. IMPORTANT: The Remedadifference
function isn’t a drop-in replacement for the Lodash_.difference
function, we do not recommend migrating directly fromwithout
to Remeda’sdifference
. -
without
takes variadic arguments;difference
takes an explicit array. You will need to wrap your items in an array when migrating.
// without
_.without(DATA, a, b, c, ...additional);
// difference
_.difference(DATA, [a, b, c, ...additional]);
Not provided by Remeda.
Compose zip
and fromEntries
:
// Lodash
zipObject(keys, values);
// Remeda
fromEntries(zip(keys, values));
Collection
Not provided by Remeda.
Use the native JS Array.prototype.every
instead.
// Lodash
every(DATA, predicate);
// Native
DATA.every(predicate);
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 */));
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
- In Lodash
forEach
first checks if the data argument is array-like (by looking for alength
prop). In Remeda the function only accepts arrays and calling it with an object would result in a TypeScript error. UseforEachObj
instead for those cases. - In Lodash
forEach
stops iterating early when the callback returnsfalse
. In Remeda the callback cannot return a value (it must returnvoid
) and this feature isn’t available. You can build apipe
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);
}),
);
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, orforEachObj
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 replicateforEachRight
. - 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);
}
groupBy
- When using
groupBy
with a callback function use Remeda’sgroupBy
function. - When using
groupBy
on an array of objects and with a property name use Remeda’sgroupByProp
instead. - When using
groupBy
on an array of primitives (e.g.,string
) and with a property name use Remeda’sgroupBy
composed withprop
.
Callback
const DATA = [6.1, 4.2, 6.3];
// Lodash
groupBy(DATA, Math.floor);
// Remeda
groupBy(DATA, Math.floor);
Property Name (on objects)
const DATA = [
{ type: "cat", name: "felix" },
{ type: "dog", toys: 3 },
];
// Lodash
groupBy(DATA, "type");
// Remeda
groupByProp(DATA, "type");
Property Name (on primitives)
const DATA = ["one", "two", "three"];
// Lodash
groupBy(DATA, "length");
// Remeda
groupBy(DATA, prop("length"));
isIncludedIn
- In Lodash
includes
also works on strings and objects; In Remeda only arrays are supported. For objects, usevalues
first. For strings prefer the native JSString.prototype.includes
. - The 3rd optional parameter
fromIndex
isn’t supported in Remeda. You can replicate it’s usage withdrop
(for non-negative indices), ortakeLast
(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));
map
map
can be used instead ofinvokeMap
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
}
});
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"]);
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))!;
sample
- Unlike Lodash, In Remeda the
sample
function returns items in the same order they appear in the input. Use theshuffle
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());
Not provided by Remeda.
Use the native JS Array.prototype.some
instead.
// Lodash
some(DATA, predicate);
// Native
DATA.some(predicate);
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"));
Function
funnel
-
debounce
can be implemented using thefunnel
utility. A reference implementation is provided below, and a more expanded version with inline documentation and tests is available in the test filefunnel.lodash-debounce.test.ts
. -
A more complete reference implementation that also maintains Lodash’s capability to store the callback’s return value is available below, and in
funnel.lodash-debounce-with-cached-value.test.ts
. -
These implementations can be copied as-is into your project, but might contain redundant parts which are not relevant for your specific use cases. By inlining only the parts you need you can take advantage of capabilities not available in Lodash.
Reference
function debounce<F extends (...args: any) => void>(
func: F,
wait = 0,
{
leading = false,
trailing = true,
maxWait,
}: {
readonly leading?: boolean;
readonly trailing?: boolean;
readonly maxWait?: number;
} = {},
) {
const {
call,
isIdle: _isIdle,
...rest
} = funnel(
() => {
if (leading || trailing) {
func();
}
},
{
minQuietPeriodMs: wait,
...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
triggerAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(call, rest);
}
With call arguments
function debounce<F extends (...args: any) => void>(
func: F,
wait = 0,
{
leading = false,
trailing = true,
maxWait,
}: {
readonly leading?: boolean;
readonly trailing?: boolean;
readonly maxWait?: number;
} = {},
) {
const {
call,
isIdle: _isIdle,
...rest
} = funnel(
(args: Parameters<F>) => {
if (leading || trailing) {
func(...args);
}
},
{
reducer: (_, ...args: Parameters<F>) => args,
minQuietPeriodMs: wait,
...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
triggerAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(call, rest);
}
With cached value
function debounce<F extends (...args: any) => any>(
func: F,
wait = 0,
{
leading = false,
trailing = true,
maxWait,
}: {
readonly leading?: boolean;
readonly trailing?: boolean;
readonly maxWait?: number;
} = {},
) {
let cachedValue: ReturnType<F> | undefined;
const { call, flush, cancel } = funnel(
(args: Parameters<F>) => {
if (leading || trailing) {
cachedValue = func(...args) as ReturnType<F>;
}
},
{
reducer: (_, ...args: Parameters<F>) => args,
minQuietPeriodMs: wait,
...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
triggerAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(
(...args: Parameters<F>) => {
call(...args);
return cachedValue;
},
{
flush: () => {
flush();
return cachedValue;
},
cancel,
},
);
}
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");
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");
funnel
-
throttle
can be implemented using thefunnel
utility. A reference implementation is provided below, and a more expanded version with inline documentation and tests is available in the test filefunnel.lodash-throttle.test.ts
. -
A more complete reference implementation that also maintains Lodash’s capability to store the callback’s return value is available below, and in
funnel.lodash-throttle-with-cached-value.test.ts
. -
These implementations can be copied as-is into your project, but might contain redundant parts which are not relevant for your specific use cases. By inlining only the parts you need you can take advantage of capabilities not available in Lodash.
Reference
function throttle<F extends (...args: any) => void>(
func: F,
wait = 0,
{
leading = true,
trailing = true,
}: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
const {
call,
isIdle: _isIdle,
...rest
} = funnel(
() => {
if (leading || trailing) {
func();
}
},
{
burstCoolDownMs: wait,
maxBurstDurationMs: wait,
invokedAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(call, rest);
}
With call arguments
function throttle<F extends (...args: any) => void>(
func: F,
wait = 0,
{
leading = true,
trailing = true,
}: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
const {
call,
isIdle: _isIdle,
...rest
} = funnel(
(args: Parameters<F>) => {
if (leading || trailing) {
func(...args);
}
},
{
reducer: (_, ...args: Parameters<F>) => args,
minQuietPeriodMs: wait,
maxBurstDurationMs: wait,
triggerAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(call, rest);
}
With cached value
function throttle<F extends (...args: any) => any>(
func: F,
wait = 0,
{
leading = true,
trailing = true,
}: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
let cachedValue: ReturnType<F> | undefined;
const { call, flush, cancel } = funnel(
(args: Parameters<F>) => {
if (leading || trailing) {
cachedValue = func(...args) as ReturnType<F>;
}
},
{
reducer: (_, ...args: Parameters<F>) => args,
minQuietPeriodMs: wait,
maxBurstDurationMs: wait,
triggerAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(
(...args: Parameters<F>) => {
call(...args);
return cachedValue;
},
{
flush: () => {
flush();
return cachedValue;
},
cancel,
},
);
}
Lang
Math
firstBy
Use "desc"
as the direction.
// Lodash
maxBy(DATA, criteria);
// Remeda
firstBy(DATA, [criteria, "desc"]);
Number
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’srandomInteger
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);
Object
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 usemerge
, but if you need to merge more than 2 objects usemergeAll
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]);
prop
- In Lodash
get
takes an array where each element is a property in the path to a deeply-nested property (e.g.['a', 'b', '0', 'c']
). In Remeda the path is provided directly as a variadic arguments list. - In Lodash all property keys are
string
, even when accessing numbers. In Remeda the exact type of each prop should be used instead (e.g.,number
when accessing arrays). - Lodash also supports a string that defines the path to the deeply-nested
property (e.g.,
a.b[0].c
). In Remeda this isn’t supported directly; instead, use the helper functionstringToPath
to convert the path string to a path array, which could then be spread intoprop
. - Lodash supports an optional last argument to
get
which defines a default fallback in cases where the prop being accessed does not exist on the input object. To achieve this wrap your call toprop
with a call todefaultTo
, or use the native Nullish coalescing operator??
instead if the fallback is of a different type.
Shallow Access
const DATA = { a: 123 };
// Lodash
_.get(DATA, ["a"]);
// Remeda
prop(DATA, "a");
Array indices
const DATA = [1, 2, 3];
// Lodash
_.get(DATA, ["0"]);
// Remeda
prop(DATA, 0);
Path Arrays
const DATA = { a: [{ b: { c: 3 } }] };
// Lodash
_.get(DATA, ["a", "0", "b", "c"]);
// Remeda
prop(DATA, "a", 0, "b", "c");
Path Strings
const DATA = { a: [{ b: { c: 3 } }] };
// Lodash
_.get(DATA, "a[0].b.c");
// Remeda
prop(DATA, ...stringToPath("a[0].b.c"));
Default Fallback
const DATA = { a: [] as Array<{ b: number }> };
// Lodash
_.get(DATA, ["a", "0", "b"], "default");
// Remeda
defaultTo(prop(DATA, "a", 0, "b"), 123);
// Or natively
prop(DATA, "a", 0, "b") ?? "default";
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 functionstringToPath
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), andaddProp
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);
Seq
String
toCamelCase
- Lodash attempts pseudo-linguistic word splitting to handle special characters,
which might lead to inaccurate results. Remeda uses a simpler word splitting
approach based on
type-fest
’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-inIntl.Segmenter
instead. - Remeda treats consecutive uppercase characters differently from Lodash. Use
{ preserveConsecutiveUppercase: false }
as the second parameter to get the same results. - Lodash performs normalization on the input before splitting it, including
deburr
and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required. - Lodash allows calling
camelCase
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Simple strings
// Lodash
_.camelCase(input);
// Remeda
toCamelCase(input, { preserveConsecutiveUppercase: false });
Normalized
// Lodash
_.camelCase(input);
// Remeda + Native
toCamelCase(
input
// "Promote" diacritics to be independent characters in the string.
.normalize("NFD")
// Remove apostrophes and all independent diacritic characters.
.replace(/['\u2019\u0300-\u036f]/g, ""),
{ preserveConsecutiveUppercase: false },
);
Missing input
// Lodash
_.camelCase();
_.camelCase(input);
// Remeda
("");
input !== undefined
? toCamelCase(input, { preserveConsecutiveUppercase: false })
: "";
// Or
toCamelCase(input ?? "", { preserveConsecutiveUppercase: false });
capitalize
- Lodash converts the entire string to lowercase first, while Remeda only
capitalizes the first character. To match Lodash behavior, call
toLowerCase
first. - For display purposes, prefer using CSS; use
text-transform: lowercase
on the element, then use::first-letter
to target just the first letter of the word, and usetext-transform: uppercase
to capitalize it.
Runtime
// Lodash
_.capitalize(input);
// Remeda
capitalize(toLowerCase(input));
CSS
// Lodash
<div>{_.capitalize(input)}</div>
// CSS
<style>
.capitalize {
text-transform: lowercase;
}
.capitalize::first-letter {
text-transform: uppercase;
}
</style>
<div class="capitalize">{input}</div>
Not provided by Remeda.
deburr
uses hard-coded heuristics which can lead to inaccurate results:
- Misses some accented characters (e.g., ḩ, ố, ῷ and й).
- Erroneously “simplifies” ligatures that don’t formally have any diacritics that require “deburring” (e.g., Æ, IJ, Œ, and ʼn).
- Swaps characters because they look similar although they are semantically unrelated to each other (e.g., Ð, Ø, Þ, ß, ſ).
We strongly recommend using linguistically-aware alternatives instead that are purpose-built for this task:
- When comparing strings, prefer
Intl.Collator
withsensitivity: 'base'
to compare them while ignoring accents and case. - When simplifying strings, prefer
normalize("NFD")
to decompose characters, then usereplace
to remove diacritics. This solution is more accurate, but is not a drop-in replacement fordeburr
because of the differences mentioned above. - When querying for a string, prefer purpose built collations and other built-in mechanisms of databases and other systems that offer more robust query normalization.
Comparison
// Lodash
_.deburr("café") === "cafe";
// Native JS
const collator = new Intl.Collator("en", { sensitivity: "base" });
collator.compare("café", "cafe") === 0;
Simplifying
// Lodash
_.deburr(input);
// Native JS
input
.normalize("NFD")
.replace(
/[\u0300-\u036f]/g /* The "Combining Diacritical Marks" Unicode block. */,
"",
);
endsWith
- Lodash supports an optional
position
parameter to check only part of the string, while Remeda doesn’t support this parameter. To replicate this behavior, usesliceString
to truncate the string before checking. - Lodash allows the suffix to be
undefined
(converts to"undefined"
string) but this is not supported by Remeda and needs to be handled explicitly.
Basic usage
// Lodash
_.endsWith(input, suffix);
// Remeda
endsWith(input, suffix);
// Native
input.endsWith(suffix);
Position parameter
// Lodash
_.endsWith(input, suffix, position);
// Remeda
endsWith(sliceString(input, 0, position), suffix);
// Native
input.endsWith(suffix, position);
Undefined suffix
// Lodash
_.endsWith(input, suffix);
// Remeda
suffix !== undefined && endsWith(input, suffix);
Not provided by Remeda.
escape
is not needed in the vast majority of modern web development - all
modern frameworks and tools perform this kind of escaping (and more!)
automatically wherever it matters. If you are working on a low-level application
that writes directly to the DOM, prefer libraries that take a holistic approach
to this problem (like DOMPurify
)
instead of basic string substitutions. In the rare case you must have an
escaping function, copy the reference code below as-is:
const ESCAPED: Readonly<Record<string, `&${string};`>> = {
// https://github.com/lodash/lodash/blob/main/lodash.js#L399-L403
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
};
const CHARS_RE = new RegExp(`[${Object.keys(ESCAPED).join("")}]`, "g");
const escape = (input: string): string =>
input.replace(CHARS_RE, (char) => ESCAPED[char]!);
Not provided by Remeda.
escapeRegExp
is effectively a wrapper around the regular expression
/[\\^$.*+?()[\]{}|]/g
(which matches on the characters: ^, $, ., *, +, ?, (),
{}, [], and |) that also does replacement. It is only useful in very niche
situations where you’d want to use externally-provided strings directly to the
RegExp constructor and you’d want to sanitize them first. These situations might
be solvable differently by either validating the input itself, or by using more
nuanced string utilities provided by databases and other data sources that might
already do sanitization for you. In the rare case you must have an escaping
function, copy the reference code below as-is.
const SPECIAL_CHARACTERS_RE = /[\\^$.*+?()[\]{}|]/g;
const escapeRegExp = (input: string): string =>
input.replace(SPECIAL_CHARACTERS_RE, "\\$&");
toKebabCase
- Lodash attempts pseudo-linguistic word splitting to handle special characters,
which might lead to inaccurate results. Remeda uses a simpler word splitting
approach based on
type-fest
’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-inIntl.Segmenter
instead. - Lodash performs normalization on the input before splitting it, including
deburr
and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required. - Lodash allows calling
kebabCase
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Simple strings
// Lodash
_.kebabCase(input);
// Remeda
toKebabCase(input);
Normalized
// Lodash
_.kebabCase(input);
// Remeda + Native
toKebabCase(
input
// "Promote" diacritics to be independent characters in the string.
.normalize("NFD")
// Remove apostrophes and all independent diacritic characters.
.replace(/['\u2019\u0300-\u036f]/g, ""),
);
Missing input
// Lodash
_.kebabCase();
_.kebabCase(input);
// Remeda
("");
input !== undefined ? toKebabCase(input) : "";
// Or
toKebabCase(input ?? "");
toTitleCase
- Use
toLowerCase
on the results oftoTitleCase
, which reformats the string into space-delimited words. - Lodash attempts pseudo-linguistic word splitting to handle special characters,
which might lead to inaccurate results. Remeda uses a simpler word splitting
approach based on
type-fest
’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-inIntl.Segmenter
instead. - Lodash performs normalization on the input before splitting it, including
deburr
and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required. - Lodash allows calling
lowerCase
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Basic usage
// Lodash
_.lowerCase(input);
// Remeda
toLowerCase(toTitleCase(input));
Normalized
// Lodash
_.lowerCase(input);
// Remeda + Native
toLowerCase(
toTitleCase(
input
// "Promote" diacritics to be independent characters in the string.
.normalize("NFD")
// Remove apostrophes and all independent diacritic characters.
.replace(/['\u2019\u0300-\u036f]/g, ""),
),
);
Missing input
// Lodash
_.lowerCase();
_.lowerCase(input);
// Remeda
("");
input !== undefined ? toLowerCase(toTitleCase(input)) : "";
// Or
toLowerCase(toTitleCase(input ?? ""));
uncapitalize
- For display purposes, prefer using CSS; use
::first-letter
to target just the first letter of the word andtext-transform: lowercase
to lowercase it. - Lodash allows calling
lowerFirst
without any input (or with anundefined
input), which results in an empty string""
. Remeda’suncapitalize
requires a string input.
Basic usage
// Lodash
_.lowerFirst(input);
// Remeda
uncapitalize(input);
CSS
// Lodash
<div>{_.lowerFirst(input)}</div>
// CSS
<style>
.lowerFirst::first-letter {
text-transform: lowercase;
}
</style>
<div class="lowerFirst">{input}</div>
Missing input data
// Lodash
_.lowerFirst();
_.lowerFirst(input);
// Remeda
input !== undefined ? uncapitalize(input) : "";
// Or
uncapitalize(input ?? "");
Not provided by Remeda.
- If
pad
is used with whitespace padding (which is also the default) in order to center text within an HTML component, prefer using CSS instead. Usewidth
with a length inch
units, andtext-align: center
. - For other uses
pad
could be replicated viapadStart
andpadEnd
, by computing the amount of effective padding you need on one side.
CSS
// Lodash
<pre>{_.pad(input, 20)}</pre>
// CSS
<div style="text-align:center;width:20ch">{input}</div>
Reference Implementation
const pad = (data: string, length: number, padding = " ") =>
data.padStart((length + data.length) / 2, padding).padEnd(length, padding);
Not provided by Remeda.
- Use native
String.prototype.padEnd
instead. - If
padEnd
is used with whitespace padding (which is also the default) in order to position text within an HTML component, prefer using CSS instead. Usewidth
with a length inch
units, andtext-align: left
.
Basic Usage
// Lodash
_.padEnd(input, n);
_.padEnd(input, n, padding);
// Native
input.padEnd(n);
input.padEnd(n, padding);
CSS
// Lodash
<pre>{_.padEnd(input, 20)}</pre>
// CSS
<div style="text-align:left;width:20ch">{input}</div>
Not provided by Remeda.
- Use native
String.prototype.padStart
instead. - If
padStart
is used with whitespace padding (which is also the default) in order to position text within an HTML component, prefer using CSS instead. Usewidth
with a length inch
units, andtext-align: right
.
Basic Usage
// Lodash
_.padStart(input, n);
_.padStart(input, n, padding);
// Native
input.padStart(n);
input.padStart(n, padding);
CSS
// Lodash
<pre>{_.padStart(input, 20)}</pre>
// CSS
<div style="text-align:right;width:20ch">{input}</div>
Not provided by Remeda.
- Use native
Number.parseInt
instead. - When used as a callback,
parseInt
protects against the index parameter unintentionally redefining theradix
. To avoid this issue, wrap the callback with an arrow function.
Basic usage
// Lodash
_.parseInt(input);
_.parseInt(input, radix);
// Native
Number.parseInt(input);
Number.parseInt(input, radix);
Callback
// Lodash
data.map(_.parseInt);
// Native
data.map((item) => Number.parseInt(item, 10)); // ✅
data.map(Number.parseInt); // ❌
Not provided by Remeda.
Use native String.prototype.repeat
instead.
// Lodash
_.repeat(input, n);
// Native
input.repeat(n);
Not provided by Remeda.
- Use native
String.prototype.replace
instead. - Lodash typing incorrectly allows calling
replace
with two arguments. These calls return strings as-is, while RegExp inputs are converted to strings.
Regular Usage
// Lodash
_.replace(input, pattern, replacement);
// Native
input.replace(pattern, replacement);
Invalid 2-parameter usage
// Lodash
_.replace(input, replacement);
_.replace(/pattern/, replacement);
// Native
input; // Returns input unchanged
String(/pattern/); // "/pattern/" - RegExp converted to string
toSnakeCase
- Lodash attempts pseudo-linguistic word splitting to handle special characters,
which might lead to inaccurate results. Remeda uses a simpler word splitting
approach based on
type-fest
’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-inIntl.Segmenter
instead. - Lodash performs normalization on the input before splitting it, including
deburr
and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required. - Lodash allows calling
snakeCase
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Simple strings
// Lodash
_.snakeCase(input);
// Remeda
toSnakeCase(input);
Normalized
// Lodash
_.snakeCase(input);
// Remeda + Native
toSnakeCase(
input
// "Promote" diacritics to be independent characters in the string.
.normalize("NFD")
// Remove apostrophes and all independent diacritic characters.
.replace(/['\u2019\u0300-\u036f]/g, ""),
);
Missing input
// Lodash
_.snakeCase();
_.snakeCase(input);
// Remeda
("");
input !== undefined ? toSnakeCase(input) : "";
// Or
toSnakeCase(input ?? "");
split
- Lodash allows calling
split
without aseparator
parameter (or setting it toundefined
) which returns the input string as a single-element array (the same happens whensplit
is used as a callback). Remeda needs the input wrapped in an array manually. - Lodash accepts
null
andundefined
input values, converting them to empty strings. Remeda needs these values handled separately. - When the
separator
parameter is""
, Lodash uses complex logic to parse compound Unicode characters (like family emojis 👨👩👧👦 or flags with modifiers 🏳️🌈) as single graphemes. Remeda uses nativeString.prototype.split
which splits these into component parts. UseIntl.Segmenter
withgranularity: "grapheme"
for proper Unicode handling. This is rarely needed - simple Unicode characters (like non-Latin alphabets) work fine.
Basic usage
// Lodash
_.split(input, separator);
_.split(input, separator, limit);
// Remeda
split(input, separator);
split(input, separator, limit);
Without separator
// Lodash
_.split(input);
_.map(data, _.split);
// Native
[input];
map(data, (item) => [item]);
Nullish inputs
// Lodash
_.split(input, separator);
_.split(input, separator, limit);
// Remeda
input != null ? split(input, separator) : [""];
input != null ? split(input, separator, limit) : [""];
// Or
split(input ?? "", separator);
split(input ?? "", separator, limit);
Unicode grapheme splitting
// Lodash
_.split(input, "");
_.split(input, "", limit);
// Native
[...new Intl.Segmenter("en", { granularity: "grapheme" }).segment(input)].map(
({ segment }) => segment,
);
[...new Intl.Segmenter("en", { granularity: "grapheme" }).segment(input)]
.map(({ segment }) => segment)
.slice(0, limit);
toTitleCase
- Lodash attempts pseudo-linguistic word splitting to handle special characters,
which might lead to inaccurate results. Remeda uses a simpler word splitting
approach based on
type-fest
’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-inIntl.Segmenter
instead. - Remeda treats consecutive uppercase characters differently from Lodash. Use
{ preserveConsecutiveUppercase: false }
as the second parameter to get the same results. - Lodash performs normalization on the input before splitting it, including
deburr
and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required. - Lodash allows calling
startCase
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Simple strings
// Lodash
_.startCase(input);
// Remeda
toTitleCase(input, { preserveConsecutiveUppercase: false });
Normalized
// Lodash
_.startCase(input);
// Remeda + Native
toTitleCase(
input
// "Promote" diacritics to be independent characters in the string.
.normalize("NFD")
// Remove apostrophes and all independent diacritic characters.
.replace(/['\u2019\u0300-\u036f]/g, ""),
{ preserveConsecutiveUppercase: false },
);
Missing input
// Lodash
_.startCase();
_.startCase(input);
// Remeda
("");
input !== undefined
? toTitleCase(input, { preserveConsecutiveUppercase: false })
: "";
// Or
toTitleCase(input ?? "", { preserveConsecutiveUppercase: false });
startsWith
- Lodash supports an optional
position
parameter to check from a specific index, while Remeda only checks from the beginning of the string. ComposesliceString
withstartsWith
to replicate this behavior. - Lodash allows calling the function without a
target
parameter (or usingundefined
for it). These would result in the function always returningtrue
. In Remeda thetarget
is required and handlingundefined
should be done before callingstartsWith
. - Lodash also allows calling the function without an
input
parameter (or usingundefined
for it). These would result in the function always returningfalse
(unlesstarget
is also""
orundefined
). This parameter is also required in Remeda; handle theundefined
explicitly.
Basic usage
// Lodash
_.startsWith(input, target);
// Remeda
startsWith(input, target);
Position parameter
// Lodash
_.startsWith(input, target, position);
// Remeda
startsWith(sliceString(input, position), target);
Missing parameters
// Lodash
_.startsWith();
_.startsWith(input);
// Native
true;
true;
Undefined target
// Lodash
_.startsWith(input, target);
// Remeda
target === undefined || startsWith(input, target);
// Or
startsWith(input, target ?? "");
Undefined input
// Lodash
_.startsWith(input, target);
// Remeda
startsWith(input ?? "", target);
// Or (if you know target is not `undefined` or `""`)
input !== undefined && startsWith(input, target);
Not provided by Remeda.
Lodash’s template
is a complex templating engine that compiles templates into
executable JavaScript functions. Due to its complexity and security
implications, it is outside the scope that Remeda offers.
- In most cases it can be substituted by native template literals.
- In many other cases it is redundant with the templating capabilities of frameworks such as React, Express, Vue, Fastify, and many others.
- For the most complex cases there are dedicated libraries that offer robust templating features (e.g., Handlebars).
Template Literals
// Lodash
const greet = _.template("Hello <%= firstName %> <%= lastName %>!");
// Template literals
const greet = ({
firstName,
lastName,
}: {
firstName: string;
lastName: string;
}) => `Hello ${firstName} ${lastName}!`;
HTML
// Lodash
const bold = _.template("<b><%- value %></b>");
bold({ value: "<script>" });
// React
function Bold({ value }: { value: string }) {
return <b>{value}</b>;
}
<Bold value="<script>" />;
With complex logic
// Lodash
const list = _.template(
"<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>",
);
list({ users: ["fred", "barney"] });
// React
function List({ users }: { users: string[] }) {
return users.map((user) => <li key={user}>{user}</li>);
}
<List users={["fred", "barney"]} />;
toLowerCase
- For display purposes, prefer using CSS
text-transform: lowercase
. - Lodash allows calling
toLower
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Basic usage
// Lodash
_.toLower(input);
// Remeda
toLowerCase(input);
CSS
// Lodash
<div>{_.toLower(input)}</div>
// CSS
<div style="text-transform:lowercase">{input}</div>
Missing input
// Lodash
_.toLower();
_.toLower(input);
// Remeda
("");
input !== undefined ? toLowerCase(input) : "";
// Or
toLowerCase(input ?? "");
toUpperCase
- For display purposes, prefer using CSS
text-transform: uppercase
. - Lodash allows calling
toUpper
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Basic usage
// Lodash
_.toUpper(input);
// Remeda
toUpperCase(input);
CSS
// Lodash
<div>{_.toUpper(input)}</div>
// CSS
<div style="text-transform:uppercase">{input}</div>
Missing input
// Lodash
_.toUpper();
_.toUpper(input);
// Remeda
("");
input !== undefined ? toUpperCase(input) : "";
// Or
toUpperCase(input ?? "");
Not provided by Remeda.
- When the second
characters
parameter is not provided to Lodash (or when it isundefined
), all whitespace characters would be trimmed. This is the same as the nativeString.prototype.trim
. - The native
trim
doesn’t support the additionalcharacters
parameter that allows changing the trimmed characters. Instead, create a regex that would matchcharacters
anchored to either the start or the end of the string (^[${characters}]+|[${characters}]+$
) and then useString.prototype.replace
to replace them with the empty string (""
). Don’t forget theg
RegExp flag to properly catch everything, and theu
RegExp flag if you need to handle Unicode characters. - Lodash does complex grapheme parsing, but this is usually not needed unless
the
characters
parameter itself contains complex Unicode graphemes (like family emojis 👨👩👧👦 or flags with modifiers 🏳️🌈 that you want to trim). In these cases useIntl.Segmenter
withgranularity: "grapheme"
to split the string, then use Remeda’sdropWhile
(for left trimming) anddropLastWhile
(for right trimming), and rejoin the array back withjoin
. - Lodash allows calling
trim
without any input (or with anundefined
input), which results in an empty string""
. This requires explicit handling in replacements.
Whitespaces
// Lodash
_.trim(input);
// Native
input.trim();
Callback
// Lodash
_.map(data, _.trim);
// Native
data.map(String.prototype.trim);
Characters
// Lodash
_.trim(input, characters);
// Native
input.replace(new RegExp(`^[${characters}]+|[${characters}]+$`, "gu"), "");
Graphemes
// Lodash
_.trim(input, characters);
// Remeda
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const inputGraphemes = Array.from(segmenter.segment(input), prop("segment"));
const graphemes = Array.from(segmenter.segment(characters), prop("segment"));
join(
dropLastWhile(
dropWhile(inputGraphemes, isIncludedIn(graphemes)),
isIncludedIn(graphemes),
),
"",
);
Missing input data
// Lodash
_.trim();
_.trim(input);
// Native
("");
input?.trim() ?? "";
Not provided by Remeda.
- When the second
characters
parameter is not provided to Lodash (or when it isundefined
), all whitespace characters would be trimmed. This is the same as the nativeString.prototype.trimEnd
. - The native
trimEnd
doesn’t support the additionalcharacters
parameter that allows changing the trimmed characters. Instead, create a regex that would matchcharacters
anchored to the end of the string ([${characters}]+$
) and then useString.prototype.replace
to replace them with the empty string (""
). Don’t forget theg
RegExp flag to properly catch everything, and theu
RegExp flag if you need to handle Unicode characters. - Lodash does complex grapheme parsing, but this is usually not needed unless
the
characters
parameter itself contains complex Unicode graphemes (like family emojis 👨👩👧👦 or flags with modifiers 🏳️🌈 that you want to trim). In these cases useIntl.Segmenter
withgranularity: "grapheme"
to split the string, then use Remeda’sdropLastWhile
, and rejoin the array back withjoin
. - Lodash allows calling
trimEnd
without any input (or with anundefined
input), which results in an empty string""
. This requires explicit handling in replacements.
Whitespaces
// Lodash
_.trimEnd(input);
// Native
input.trimEnd();
Callback
// Lodash
_.map(data, _.trimEnd);
// Native
data.map(String.prototype.trimEnd);
Characters
// Lodash
_.trimEnd(input, characters);
// Native
input.replace(new RegExp(`[${characters}]+$`, "gu"), "");
Graphemes
// Lodash
_.trimEnd(input, characters);
// Remeda
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const inputGraphemes = Array.from(segmenter.segment(input), prop("segment"));
const graphemes = Array.from(segmenter.segment(characters), prop("segment"));
join(dropLastWhile(inputGraphemes, isIncludedIn(graphemes)), "");
Missing input data
// Lodash
_.trimEnd();
_.trimEnd(input);
// Native
("");
input?.trimEnd() ?? "";
Not provided by Remeda.
- When the second
characters
parameter is not provided to Lodash (or when it isundefined
), all whitespace characters would be trimmed. This is the same as the nativeString.prototype.trimStart
. - The native
trimStart
doesn’t support the additionalcharacters
parameter that allows changing the trimmed characters. Instead, create a regex that would matchcharacters
anchored to the start of the string (^[${characters}]+
) and then useString.prototype.replace
to replace them with the empty string (""
). Don’t forget theg
RegExp flag to properly catch everything, and theu
RegExp flag if you need to handle Unicode characters. - Lodash does complex grapheme parsing, but this is usually not needed unless
the
characters
parameter itself contains complex Unicode graphemes (like family emojis 👨👩👧👦 or flags with modifiers 🏳️🌈 that you want to trim). In these cases useIntl.Segmenter
withgranularity: "grapheme"
to split the string, then use Remeda’sdropWhile
, and then rejoin the array back withjoin
. - Lodash allows calling
trimStart
without any input (or with anundefined
input), which results in an empty string""
. This requires explicit handling in replacements.
Whitespaces
// Lodash
_.trimStart(input);
// Native
input.trimStart();
Callback
// Lodash
_.map(data, _.trimStart);
// Native
data.map(String.prototype.trimStart);
Characters
// Lodash
_.trimStart(input, characters);
// Native
input.replace(new RegExp(`^[${characters}]+`, "gu"), "");
Graphemes
// Lodash
_.trimStart(input, characters);
// Remeda
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const inputGraphemes = Array.from(segmenter.segment(input), prop("segment"));
const graphemes = Array.from(segmenter.segment(characters), prop("segment"));
join(dropWhile(inputGraphemes, isIncludedIn(graphemes)), "");
Missing input data
// Lodash
_.trimStart();
_.trimStart(input);
// Native
("");
input?.trimStart() ?? "";
truncate
- Remeda requires an explicit
length
parameter, while Lodash defaults to30
when it isn’t provided. - Lodash computes the length of the
input
andomission
strings in graphemes, unlike Remeda that counts Unicode characters. In the vast majority of cases these are identical, but when these strings contain complex Unicode characters (like family emojis 👨👩👧👦 or flags with modifiers 🏳️🌈) the input might not be truncated at the same index. - The Remeda function would never return a string longer than
length
, opting to truncate theomission
itself when it’s too long. Lodash will never truncate theomission
and therefore might return an output that exceedslength
.
Default length
// Lodash
_.truncate(input);
// Remeda
truncate(input, 30);
Custom length
// Lodash
_.truncate(input, { length });
// Remeda
truncate(input, length);
With options
// Lodash
_.truncate(input, { omission, separator });
_.truncate(input, { length, omission, separator });
// Remeda
truncate(input, 30, { omission, separator });
truncate(input, length, { omission, separator });
Not provided by Remeda.
unescape
is rarely needed in modern web development. The main use case is
processing HTML documents/snippets. For comprehensive HTML processing, prefer
dedicated HTML/XML parsing libraries. In the rare case you must have an
unescaping function, copy the reference code below as-is:
const UNESCAPED: Readonly<Record<`&${string};`, string>> = {
// https://github.com/lodash/lodash/blob/main/lodash.js#L408-L412
"&": "&",
"<": "<",
">": ">",
""": '"',
"'": "'",
};
const ENTITIES_RE = new RegExp(`${Object.keys(UNESCAPED).join("|")}`, "g");
const unescape = (input: string): string =>
input.replace(ENTITIES_RE, (entity) => UNESCAPED[entity]!);
toTitleCase
- Use
toUpperCase
on the results oftoTitleCase
, which reformats the string into space-delimited words. - Lodash attempts pseudo-linguistic word splitting to handle special characters,
which might lead to inaccurate results. Remeda uses a simpler word splitting
approach based on
type-fest
’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-inIntl.Segmenter
instead. - Lodash performs normalization on the input before splitting it, including
deburr
and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required. - Lodash allows calling
upperCase
without any input, or with anundefined
input, which isn’t supported in Remeda. Handle these cases before calling the function.
Basic usage
// Lodash
_.upperCase(input);
// Remeda
toUpperCase(toTitleCase(input));
Normalized
// Lodash
_.upperCase(input);
// Remeda + Native
toUpperCase(
toTitleCase(
input
// "Promote" diacritics to be independent characters in the string.
.normalize("NFD")
// Remove apostrophes and all independent diacritic characters.
.replace(/['\u2019\u0300-\u036f]/g, ""),
),
);
Missing input
// Lodash
_.upperCase();
_.upperCase(input);
// Remeda
("");
input !== undefined ? toUpperCase(toTitleCase(input)) : "";
// Or
toUpperCase(toTitleCase(input ?? ""));
capitalize
- For display purposes, prefer using CSS; use
::first-letter
to target just the first letter of the word andtext-transform: uppercase
to capitalize it. - Lodash allows calling
upperFirst
without any input (or with anundefined
input), which results in an empty string""
. Remeda’scapitalize
requires a string input.
Basic usage
// Lodash
_.upperFirst(input);
// Remeda
capitalize(input);
CSS
// Lodash
<div>{_.upperFirst(input)}</div>
// CSS
<style>
.upperFirst::first-letter {
text-transform: uppercase;
}
</style>
<div class="upperFirst">{input}</div>
Missing input data
// Lodash
_.upperFirst();
_.upperFirst(input);
// Remeda
input !== undefined ? capitalize(input) : "";
// Or
capitalize(input ?? "");
Not provided by Remeda.
words
is often used to convert between different cases of identifiers and keys. UsetoCamelCase
,toKebabCase
,toSnakeCase
, ortoTitleCase
instead.- If
words
is used for simple splitting tasks, it can often be replaced withString.prototype.split
using simple regular expressions like/\s+/
,/\W+/
,/[\p{Z}\p{P}]+/u
, or ones tailored specifically for your use-case. - Lodash performs a lot of pseudo-linguistic heuristics in order to detect
special characters like diacritics, emojis, and complex graphemes. If you need
accurate language-aware splitting of words, prefer
Intl.Segmenter
withgranularity: "word"
. - When provided with the optional
pattern
parameter,words
defers the call toString.prototype.match
as-is.
Case conversion
// Lodash
_.words(input).map(_.toLowerCase).join("-");
_.words(input).map(_.toLowerCase).join("_");
_.words(input).map(_.capitalize).join(" ");
_.words(input).map(_.toUpperCase).join("-");
_.words(input).map(_.toUpperCase).join("_");
// Remeda
toKebabCase(input);
toSnakeCase(input);
toTitleCase(input, { preserveConsecutiveUppercase: false });
toUpperCase(toKebabCase(input));
toUpperCase(toSnakeCase(input));
Naive splitting
// Lodash
_.words(input);
// Remeda
split(input, /\s+/); // spaces
split(input, /\W+/); // spaces, punctuation
split(input, /[\p{Z}\p{P}]+/u); // support for unicode spaces and punctuation.
Proper NLP
// Lodash
_.words(input);
// Native
const segmenter = new Intl.Segmenter("en", { granularity: "word" });
Array.from(segmenter.segment(input)).map(({ segment }) => segment);
Custom pattern
// Lodash
_.words(input, pattern);
// Native
input.match(pattern);
Util
defaultTo
Number.NaN
is not considered nullish and would not result in returning the default value. Usewhen
with the built-inNumber.isNaN
and aconstant
for the fallback value.- Unlike in Remeda, Lodash allows the fallback value to be of any type, even
those that are incompatible with the data type. It also allows the data type
to be non-nullish; for those cases use the built-in Nullish coalescing operator
??
directly, or usewhen
withisNullish
andconstant
.
Nullish
const DATA: number | undefined | null;
// Lodash
_.defaultTo(DATA, 456);
// Remeda
defaultTo(DATA, 456);
NaN
const DATA = Number.NaN;
// Lodash
_.defaultTo(DATA, 10);
// Remeda
when(DATA, Number.isNaN, constant(10));
Both
const DATA: number | null | undefined;
// Lodash
_.defaultTo(DATA, 10);
// Remeda
when(
DATA,
(x) => x === undefined || x === null || Number.isNaN(x),
constant(10),
);
Non-matching fallback
const DATA: string | null | undefined;
// Lodash
_.defaultTo(DATA, 123);
// Remeda
when(DATA, isNullish, constant(123));
// or natively
DATA ?? 123;
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());