So why use types in the first place? Isn’t the dynamic and flexible nature of JavaScript what makes it so great in the first
place? As strange as it sounds, I actually believe that adding more constraints to the system can result in more freedom.
Of course, the type system has to be powerful and flexible enough to not get in your way.

This post is meant for developers who work with JavaScript and has an interest in TypeScript and React. I will be showing
examples using the Redux framework, but it is not a requirement. No prior knowledge of TypeScript is required, but you should have
some familiarity with React.

I will cover the following topics:

What are types, and why we need them?

How TypeScript can help us develop React applications.

Additional considerations when choosing TypeScript and React.

Update (2017-10-12): I've updated the code to use TypeScript 2.5.3, ReactJS 16.0.0, and Redux 3.7.2. It now uses typings as opposed to tsd, which is the direction the community has adopted.

Types are good for you

If you have experience in Java (or in a similar statically typed language), you may be immediately turned off by types.
The first thing to note is that a type in TypeScript is not a class. A more mathematical definition of a type is that a
type is a name given to the set of inputs and outputs of a given function. That is, a type describes the interface of a function.
This is the way I think about types.

Say I have a function defined as follows:

constadd=(a:number,b:number):number=>a+b;

The add function above says that it takes in two parameters a and b both of type number, then outputs
a number. Thus, if I write any of the following statements, I will get a compile error.

// '2' is not a number.add(1,'2');// result declared to be string, but add returns a number.constresult:string=add(1,2);

If you are using an IDE that supports TypeScript you will get editor hints as you are typing.

As seen above, types gives us two benefits.

Catch errors early on during compile time, not runtime.

Serves as documentation for functions. And in editors that support TypeScript, we get type hints as we code.

Expanding on point #2, the type hints are especially useful for options object, as seen in libraries like jQuery.

Say I have this function defined below.

// The | here denotes a union type, which will be covered later in this post.// options? means the second parameter is not required.constdoSomething=(x:number|string|CustomClass,options?:Options):void=>{// ...};

As a user of doSomething I might wonder what options I am allowed to pass in. With the Options type defined
below, I will get hints in our editor as I code.

What is more interesting is that I can use the type to form an interface over a function that operate over Todos.

constcompletedTodos=(todos:Todo[]):Todo[]=>{returntodos.filter(t=>t.completed);};// This works.completedTodos([{id:1,text:'Do something',completed:true},{id:2,text:'Do another thing',completed:false}]);// These fail because inputs are the wrong type.completedTodos('Huh?');completedTodos([{text:'Missing completed property'}]);

Again, you will see errors in editors that support TypeScript as you are typing.

Union types and type guards

Notice that there are no interfaces or subclasses defined above for numberOrString. Union types can be combined with type
guards to help manage branches within a function.

constdoubleIfNumber=(x:numberOrString):numberOrString=>{if(typeofx==='number'){returnx*2;// This is OK because we know x is a number!}else{returnx;}}doubleIfNumber(2);// 4doubleIfNumber('2');// '2'

The above features are enough for us to jump into the TodoMVC application. If you want to learn more about TypeScript,
I found the Handbook to be a useful resource.

TodoMVC in React, Redux, and TypeScript

By now you have seen some of the benefits of using a powerful type system. But how does this fit in with React?

With TypeScript 1.6, you can now write your React components in TSX files. The following is an example from my TodoMVC
example.

// `The * as ____` here is a way to import ES6 default exports in ES6.import*asReactfrom'react';interfaceTodoTextInputProps{onSave:Function;text?:string;placeholder?:string,editing?:boolean;newTodo?:boolean;}// This component's props have to match TodoTextInputProps inferface.// We can do the same for the component's state. In this example, it is set to any.classTodoTextInputextendsReact.Component<TodoTextInputProps,any>{render(){// ...}}

Note: Since React does not provide TypeScript definitions,
we cannot use it without providing ambient module definitions (e.g. .d.ts files).
In this case, I am using a tool called typings
to install the missing definitions.

You can view the typings.json
file to see all of the definitions I have installed.

Properties interface vs React.PropTypes

Properties interface differs from React.PropTypes in that the latter will only give you errors during runtime. With
Properties interface we can get feedback immediately from the compiler.

// This errors on compile because onSave is required but not specified.<TodoTextInput/>

// This errors because onSave is the wrong type.<TodoTextInputonSave={true}/>

// This errors because onEdit is not a valid property.<TodoTextInputonEdit={()=>{}}onSave={()=>{}}/>

Unfortunately, as of this writing Visual Studio Code (the editor I am using) does not seem to support TSX files yet.
The above errors were from Webpack with ts-loader.

Types and Redux

In the TodoMVC example, I defined a Todo type that is used throughout the application. It is used as the return
type of
actions/todos
. The state of
reducers/todos
is Todo[]. And thecomponentsall take in
Todo or Todo[] as properties.

This ensures that as I am coding, my actions and reducers (stores) all work with the correct types.

/* actions/todos.ts */// This action takes in a `string` and returns `Todo` as its payload.constaddTodo=createAction<Todo>(types.ADD_TODO,(text:string)=>({text,completed:false}));/* reducers/todos.ts */// This reducer takes current state of Todo[] and returns a new state of Todo[].exportdefaulthandleActions<Todo[]>({[ADD_TODO]:(state:Todo[],action:Action):Todo[]=>{return[{id:state.reduce((maxId,todo)=>Math.max(todo.id,maxId),-1)+1,completed:action.payload.completed,text:action.payload.text},...state];}// ...},initialState);

I now benefit from all the type hints and errors when working with my actions and reducers.

Using union types as models

Let’s add an initializing state to the todos reducer by introducing a Model union type for it.

// This denotes that the state is still initializing.// There is a progress property that is a number from 0-100 (%).exportinterfaceInitializing{progress:number;};// The model of our state can be either of these types.exporttypeModel=Initializing|Todo[];// Check if x is a type of Initializing. This can be used in type guards.exportconstisInitializing=(x:any):xisInitializing=>{returntypeofx.progress==='number';}

We’ll have to change our reducer’s state from type Todo[] to Model, and add type guards to our
reduce functions.

exportdefaulthandleActions<Model>({[ADD_TODO]:(state:Model,action:Action):Model=>{lettodos:Todo[];// Type guard for state.if(isInitializing(state)){// If current state is initializing, set todos to empty array.todos=[];}else{// Otherwise set to current state.todos=state;}return[{id:todos.reduce((maxId,todo)=>Math.max(todo.id,maxId),-1)+1,completed:action.payload.completed,text:action.payload.text},...todos];}// ...});

The type guard ensures that in the else branch, state is of type Todo[], which is why the above code compiles.

Note: I chose to not use a class for Initializing, but used an interface instead. The
downside of this approach is that you cannot use state instanceof Initializing in the type guard since
interfaces are not available for reflection during runtime. It's up to you how you want to implement your own types.

Rendering the initializing state

Within my App component, I can use the same Model and type check function to render an initializing state.

The alternative to this approach might be to change our state to the following type.

// todos can now be undefined or null.interfaceModel{todos?:Todo[];isInitializing:boolean;initializationProgress:number;}

This trades in the union type for a boolean flag to let us know why todos is undefined or null. This makes
our state much harder to reason about when we add in more and more flags and metadata. From my experience with real-world
applications, doing this type of thing is hard to avoid without types.

This initialization example is a bit contrived, but I hope it shows the power behind the technique. I have a
branch
of that shows how the above code works in the TodoMVC app. Fair warning, it is not completely functional because I only
did enough work to get a few examples.

Closing Remarks

In this post I offered a glimpse of how TypeScript can help you when writing a React application. Some benefits you
will receive are: