Let’s say, you wanna use/are using React, and you made a decision to use a typed JavaScript with it … and you pick TypeScript for the job. Let me congratulate you for this decision in a first place, as your Dev life is going to be much easier from now on thanks to the type safety and top notch DX in the first place! Anyways, you’ll start developing your first pieces of your React app with TypeScript. Everything will go flawlessly, until you’ll come to this first “huge” issue. Dealing with defaultProps in your components...

UPDATE — August 2018 I’ve released rex-tils library, which includes solution discussed within this article. Check it out!

This post is based on TypeScript 2.9 and uses a strict mode. If you don’t use strict mode, turn it on ASAP because not using strict mode is like cheating on your girlfriend and you don’t wanna do that. right? ( if you’re in gradual migration phase from JS to TS, nonStrict is OK ! ) In this article I will demonstrate the issue and how to solve it via class Components.

So let’s define a Button component, with following API, which will be used across this blogpost.

Button API

onClick ( click handler )

color ( what color will be used )

type (button type ‘button’ or ‘submit’)

We will annotate color and type as optional, because they will be defined via defaultProps, so consumer of our component doesn't have to provide those.

Button component implementation

Now when we use it within our root App component, we get correct optional and required props compile time checking/intellisense:

Top notch DX/Intellisense within JSX/templates — courtesy of TypeScript 👌❤️

Everything works and it’s typed. Beautiful. We can go home now… Well not so fast my friends!

Our Button s defaultProps are not typed at all, because type checker cannot infer types from generic class extentions definition to its static properties.

What does that even mean?

you can set anything to your static defaultProps

your are defining same things twice ( types and implementation )

No type checking for defaultProps:

no type checking for default props

We can fix this by extracting color and type type properties to separate type and then use type intersection by mapping our defaults to be optional via Partial mapped type helper from TS standard library.

Then we need to explicitly annotate our static defaultProps: DefaultProps which will get us proper type safety/DX within our defaultProps implementation!

proper type checking for defaultProps within component implementation

Last thing what I tend to do, is to extract defaultProps and initialState (if state is used) to separate constants, which will give us also another benefit → obtaining type definition from implementation, which introduces less boilerplate in your codebase and only one source of truth → the implementation.

defaultProps — defining source of truth for both implementation and type system

So far so good.

Let’s introduce some logic to our component shall we?

Let’s say we don’t wanna use just css inline styles ( which is an antipattern/bad performance ), and based on color prop, we wanna generate appropriate css class with some pre defined style definition.

We’ll define a resolveColorTheme function, which will accept our color prop and as outcome we will get css className.

Button component with logic for resolving className

With this, we will get a compile error! oh no! panic!

TS Error:

Type 'undefined' is not assignable to type '"blue" | "green" | "red"'

compile error introduced by optional props

Why do we get an error now? Well color is optional, and we are in strict mode, which means, that the type union is extended by an undefined / void type, but our function doesn't accept undefined . This is also compiler at it's best, which tries to protect us to adhere to proper program execution ( remember the times undefined is not a function ? ).

How to fix this a.k.a solving the million dollar problem?

As of today June 2018/TypeScript 2.9 there are 4 options how to fix this:

Non-null assertion operator

Component type casting

High order function for defining defaultProps

Props getter function

Let’s take a look at those one by one.

1. Non-null assertion operator

This solution is a no brainer, all you need to do is tell the type checker explicitly that hey dude, this won’t be null or undefined, trust me I’m an human…ehm 🤖… This is achieved by non-null assertion operator !

using non-null assertion operator within render method

This might be ok for simple use cases ( like small props API, accessing particular props only in render method ), but once your component starts to grow, it can get messy and confusing pretty quickly. Also you need to double check all the time which prop is defined as defaultProps -> more cognitive overhead for developer === bad DX / error prone

2. Component type casting

So how to solve our problem with mitigating all the pittfals mentioned in first solution?

We can create our component via anonymous class and assign it to constant which we will cast to final outcome component with proper prop types while keeping all “defaultProps” as defined within our implementation

Component casting via indirection definition

This solves our former problem, but somehow it feels like a dirty hack to me.

Can we improve this somehow? Well TypeScript 2.8 introduced a very powerful feature — conditional types. Let’s use the new and shinny feature with more functional approach, shall we ?

3. High order function for defining defaultProps

We can define a factory function/high order function, for declaring defaultProps and leveraging conditional types to correctly resolve our props API.