Announcing: A Free Book, A New Course, A Huge Price Cut...
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
There's a common issue that comes up whenever you try to use Array.reduce
to transform an array into an object. Learning how to fix it can teach you a lot about how to handle Array.reduce
in general.
Here's a playground: see if you can fix the error in the code below.
The error above is happening because Array.reduce
is inferring the type of obj
to be {}
:
const grouped = array .reduce ((obj , item ) => {
// obj[item.key] = item.value;
return obj ;
}, {});
This is happening because of the second argument we're passing into Array.reduce
: the empty object. TypeScript is inferring the type of obj
to be the same as the type of the second argument.
This is a bit of a papercut when you first get used to using Array.reduce
. If you're doing any kind of mutation inside the reduce, TypeScript won't be able to infer the type of the object you're creating.
So, we need to alert TypeScript that the object we're passing in is not just an empty object. It's an object with a specific shape that we're going to build up over the course of the reduce.
The first solution is to use as
on the second argument to Array.reduce
:
const grouped = array .reduce ((obj , item ) => {
obj [item .key ] = item .value ;
return obj ;
}, {} as Record <string, string>);
Now, TypeScript has more information about the type of obj
. It knows that it's an object with string keys and string values.
The as
is relatively safe here because we're not using it to lie to TypeScript - we're using it to give TypeScript more information.
There is a solution that doesn't involve type assertions, though.
The second solution is to annotate the type of the obj
parameter:
const grouped = array .reduce (
(obj : Record <string, string>, item ) => {
obj [item .key ] = item .value ;
return obj ;
},
{}
);
This does the same thing as the as
solution. Instead of hinting to TypeScript via the second argument, we're annotating the parameter directly.
There's another solution that's equally as safe:
The third solution is to pass a type argument to Array.reduce
:
const grouped = array .reduce <Record <string, string>>(
(obj , item ) => {
obj [item .key ] = item .value ;
return obj ;
},
{}
);
This solution relies on understanding how Array.reduce
's TypeScript inference works. It has a single type parameter that captures the type that the reducer function returns.
By passing a type argument to Array.reduce
, we're overriding the type that TypeScript infers for the reducer function.
The error you get when using Array.reduce
is a common one. It's a good example of how TypeScript's type inference can occasionally get in your way.
The solutions to this error are all relatively safe. They all involve giving TypeScript more information about the type of the object you're creating.
I prefer solutions 2 or 3, as they don't involve using as
- but each are useful to have in your toolbox.
Share this article with your friends
It's a massive ship day. We're launching a free TypeScript book, new course, giveaway, price cut, and sale.
Learn why the order you specify object properties in TypeScript matters and how it can affect type inference in your functions.
Learn how to use corepack
to configure package managers in Node.js projects, ensuring you always use the correct one.
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
Discover when it's appropriate to use TypeScript's any
type despite its risks. Learn about legitimate cases where any
is necessary.
Learn why TypeScript's types don't exist at runtime. Discover how TypeScript compiles down to JavaScript and how it differs from other strongly-typed languages.