How To Strongly Type process.env
Learn how to strongly type process.env in TypeScript by either augmenting global type or validating it at runtime with t3-env.
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 {}
:
ts
constgrouped =array .reduce ((obj ,item ) => {// obj[item.key] = item.value;returnobj ;}, {});
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
:
ts
constgrouped =array .reduce ((obj ,item ) => {obj [item .key ] =item .value ;returnobj ;}, {} asRecord <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:
ts
constgrouped =array .reduce ((obj :Record <string, string>,item ) => {obj [item .key ] =item .value ;returnobj ;},{});
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
:
ts
constgrouped =array .reduce <Record <string, string>>((obj ,item ) => {obj [item .key ] =item .value ;returnobj ;},{});
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
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.
Improve React TypeScript performance by replacing type & with interface extends. Boost IDE and tsc speed significantly.
In this book teaser, we discuss deriving vs decoupling your types: when building relationships between your types or segregating them makes sense.
Learn how TypeScript's new utility type, NoInfer, can improve inference behavior by controlling where types are inferred in generic functions.