Use isTruthy
as the predicate.
// Lodash
compact(DATA);
// Remeda
filter(DATA, isTruthy);
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.
// Lodash
difference([2, 1], [2, 3]);
// Remeda
difference([2, 1], [2, 3]);
// Lodash
difference([1, 1, 2, 2], [1]);
// Remeda
filter([1, 1, 2, 2], isNot(isIncludedIn([1])));
Either use with no params, or with a depth of 1.
// Lodash
flatten(DATA);
// Remeda
flat(DATA);
flat(DATA, 1 /* depth */);
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); // ❌
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
.
// Lodash
intersection([2, 1], [2, 3]);
// Remeda
intersection([2, 1], [2, 3]);
// Lodash
intersection([1, 1], [1, 1]);
// Remeda
unique(intersection([1, 1], [1, 1]));
Not provided by Remeda.
0
use first
.-1
use last
.data[n]
.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);
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);
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));
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
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.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.// Lodash
forEach([1, 2, 3], (item) => console.log(item));
// Remeda
forEach([1, 2, 3], (item) => {
console.log(item);
});
// Lodash
forEach({ a: 1, b: 2, c: 3 }, (value) => console.log(value));
// Remeda
forEachObj({ a: 1, b: 2, c: 3 }, (value) => {
console.log(value);
});
// 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.
for
loop.forEach
for arrays, or forEachObj
for
objects.entries
maintains the same
order, and could be used to replicate forEachRight
.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);
}
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
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
.fromIndex
isn’t supported in Remeda. You can
replicate it’s usage with drop
(for non-negative indices), or
takeLast
(for non-positive indices).// Lodash
includes([1, 2, 3], 1);
// Remeda
isIncludedIn(1, [1, 2, 3]);
// Lodash
includes({ a: 1, b: 2 }, 1);
// Remeda
isIncludedIn(1, values({ a: 1, b: 2 }));
const DATA = "Hello, World!";
// Lodash
includes(DATA, "lo");
// Native
DATA.includes("lo");
// Lodash
includes([1, 2, 3], 1, 2);
// Remeda
isIncludedIn(1, drop([1, 2, 3], 2));
// Lodash
includes([1, 2, 3], 1, -2);
// Remeda
isIncludedIn(1, takeLast([1, 2, 3], 2));
map
can be used instead of invokeMap
by using a callback functions instead
of (string) function names.// Lodash
invokeMap(
[
[5, 1, 7],
[3, 2, 1],
],
"sort",
);
// Remeda
map(
[
[5, 1, 7],
[3, 2, 1],
],
(array) => array.sort(),
);
// Lodash
invokeMap([123, 456], String.prototype.split, "");
// Remeda
map([123, 456], (num) => num.toString().split(""));
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
}
});
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
function in Remeda takes a required second parameter of the size
of the sample. Use 1.only
.values
.sample
in an extremely hot path where you need the most
efficient implementation possible, prefer a native implementation instead.// 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 */);
// Lodash
sample(DATA);
// Native
DATA[Math.floor(Math.random() * DATA.length)]!;
DATA.at(Math.floor(Math.random() * DATA.length))!;
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.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);
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"));
debounce
can be implemented using the funnel
utility. A reference
implementation is provided below, and a more expanded version with inline
documentation and tests is available in the test file funnel.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.
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();
}
},
{
burstCoolDownMs: wait,
...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
invokedAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(call, rest);
}
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,
burstCoolDownMs: wait,
...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
invokedAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(call, rest);
}
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,
burstCoolDownMs: wait,
...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
invokedAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(
(...args: Parameters<F>) => {
call(...args);
return cachedValue;
},
{
flush: () => {
flush();
return cachedValue;
},
cancel,
},
);
}
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");
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");
throttle
can be implemented using the funnel
utility. A reference
implementation is provided below, and a more expanded version with inline
documentation and tests is available in the test file funnel.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.
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);
}
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,
burstCoolDownMs: wait,
maxBurstDurationMs: wait,
invokedAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(call, rest);
}
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,
burstCoolDownMs: wait,
maxBurstDurationMs: wait,
invokedAt: trailing ? (leading ? "both" : "end") : "start",
},
);
return Object.assign(
(...args: Parameters<F>) => {
call(...args);
return cachedValue;
},
{
flush: () => {
flush();
return cachedValue;
},
cancel,
},
);
}
Use "desc"
as the direction.
// Lodash
maxBy(DATA, criteria);
// Remeda
firstBy(DATA, [criteria, "desc"]);
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.random
parameters are optional. In Remeda all parameters are
required.// Lodash
random(1, 10);
// Remeda
randomInteger(1, 10);
// Lodash
random(10);
// Remeda
randomInteger(0, 10);
// Lodash
random();
// Remeda
randomInteger(0, 1);
random(1.5, 3.5);
random(1.5);
random(10, true);
random(5, 10, true);
random(true);
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]);
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.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);
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.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.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)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);
type-fest
uses.
This might work slightly different from Lodash.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";
The remeda implementation doesn’t perform deburring (changing of accented latin characters to simple LATIN-1 characters), and doesn’t remove apostrophes.
The remeda implementation doesn’t perform deburring (changing of accented latin characters to simple LATIN-1 characters), and doesn’t remove apostrophes.
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
] with isNullish
as the predicate
and the fallback value wrapped with constant
.NaN
values use the built-in Number.isNaN
instead.// Lodash
defaultTo(DATA, 10);
// Remeda
when(DATA, isNullish, constant(10));
// Lodash
defaultTo(DATA, 10);
// Remeda
when(DATA, Number.isNaN, constant(10));
// Lodash
defaultTo(DATA, 10);
// Remeda
when(
DATA,
(x) => x === undefined || x === null || Number.isNaN(x),
constant(10),
);
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());