TypeScript 3


Copyright © 2018 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Commissioning Editor: Amarabha Banerjee
Acquisition Editor: Devanshi Doshi
Content Development Editor: Francis Carneiro
Technical Editor: Surabhi Kulkarni
Copy Editor: Safis Editing
Project Coordinator: Kinjal Bari
Proofreader: Safis Editing
Indexer: Rekha Nair
Graphics: Alishon Mendonsa
Production Coordinator: Aparna Bhagat
First published: November 2018
Production reference: 1281118
Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham
B3 2PB, UK.
ISBN 978-1-78961-025-3

Mapt is an online digital library that gives you full access to over 5,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Get a free eBook or video every month
Mapt is fully searchable
Copy and paste, print, and bookmark content
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.packt.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at customercare@packtpub.com for more details.
At www.packt.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
Carl Rippon has been involved in the software industry for over 20 years, developing a complex line of business applications in various sectors. He has spent the last eight years building single page applications using a wide range of JavaScript technologies, including Angular, ReactJS, and TypeScript. Carl has written over 100 blog posts on various technologies.
Ashok Kumar S has been working in the mobile development domain for about six years. In his early days, he was a JavaScript and Node developer. Thanks to his strong web development skills, he has mastered web and mobile development. He is a Google-certified engineer, a speaker at global level conferences (including DroidCon Berlin and MODS), and he also runs a YouTube channel called AndroidABCD for Android developers. He is a computer science and engineering graduate who is passionate about innovation in technology. He contributes to open source heavily with a view to improving his e-karma.
He has also written books on Wear OS programming and mastering the Firebase toolchain. In his spare time, he writes articles and makes videos on programming. Ashok Kumar has also reviewed books on mobile and web development, namely Mastering JUnit 5, Android Programming for Beginners, and developing Enterprise applications using JavaScript.
Dave has had over 16 years' experience as a software engineer. After working for a number of creative agencies in London and then as a contract tech lead for a global e-commerce company, he is now tech lead at Seccl Technology, a start-up based in Bath, UK, that is building pioneering digital services for the financial industry, working with a serverless infrastructure in the cloud, and providing wonderful experiences through their web applications. He has worked with TypeScript for about five years and has seen it mature a lot over the years. Dave has worked specifically on React applications for over two years, as well on serverless code for the Seccl platform.
Pogo Kid is Dave's consultancy where he provides support for companies wanting to improve their serverless, React, and TypeScript applications. He has also reviewed a couple of book proposals for Manning on TypeScript.
He has had the privilege of bringing leadership to development teams across the world, including many household names. He is a firm believer that when systems have the correct architecture and the team has a good mix of passion and skill, users will have a wonderful experience.
Daniel Deutsch is working as a web developer in various companies. Although most of his work is structured around client-side development, he is also able to contribute to different problem areas in software development, like the backend, devops, or project management. Coming from legal fields and also studying law he aims for bringing the 2 areas together and create additional value for both industries. As his personal interest focuses on machine learning, he likes to incorporate more of those disciplines in his day to day work.
Daniel's ultimate goal is to structure a business around motivated people to create something that brings value to humanity and lasts for a long time period.
If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
React was built by Facebook in order to provide more structure to their code base and allow it to scale much better. React worked so well for Facebook that they eventually open sourced it. Today, React is one of the most popular JavaScript libraries for building frontends. It allows us to build small, isolated, and highly reusable components that can be composed together in order to create complex frontends.
TypeScript was built by Microsoft to help developers more easily build large frontend applications. It is a superset of JavaScript, bringing a rich type system to it. This type system helps developers to catch bugs early and allows tools to be created to robustly navigate and refactor code.
This book will teach you how you can use both of these technologies to create large sophisticated frontends efficiently that are easy to maintain.
This book is primarily aimed at web developers who want to create large web applications with React and TypeScript. A basic understanding of JavaScript and HTML is assumed.
Chapter 1, TypeScript Basics, introduces the TypeScript type system, covering the basic types. It moves on to cover how we can configure the incredibly flexible TypeScript compiler. Linting and code formatting are also introduced, along with their configuration.
Chapter 2, What is New in TypeScript 3, steps through the significant new features that were introduced in version 3 of TypeScript. Tuples feature heavily in this chapter, along with the closely related rest and spread syntax and how we can use these constructs with strong types. Setting up multiple related TypeScript projects efficiently is also covered, before moving on to improvements that have been made when setting default prop values in a React component.
Chapter 3, Getting Started with React and TypeScript, begins with how projects that use both these technologies can be created. The chapter then introduces how strongly-typed React components can be built in both a class-based and functional manner. Managing state and hooking into life cycle events are also key topics in this chapter.
Chapter 4, Routing with React Router, introduces a library that can help us efficiently create an app with multiple pages. It covers how to create page links, and declare the components that should be rendered. Step by step, the chapter covers how to implement route parameters, query parameters, and nested routes. The chapter also covers how to load components from a route on demand in order to optimize performance in apps entailing lots of large pages.
Chapter 5, Advanced Types, focuses solely on TypeScript types. On this occasion, more advanced, but still very useful, types are covered, such as generic types, union types, overload signatures, and keyof and lookup types.
Chapter 6, Component Patterns, covers a number of common patterns for building React components while still maintaining strong types. Container components are stepped through first, followed by composite components. The popular render props pattern and higher-order components are also covered in this chapter.
Chapter 7, Working with Forms, covers how forms can be implemented efficiently with React and TypeScript. A generic form component is built step by step, including validation and submission.
Chapter 8, React Redux, covers how this popular library can help manage state across an app. A strongly typed Redux store is built with actions and reducers. The chapter finishes by looking at how a new React function can allow a Redux style structure within components without Redux.
Chapter 9, Interacting with RESTful APIs, begins with detailed coverage of asynchronous code. The chapter then moves on to cover how we can interact with RESTful APIs using a native JavaScript function, as well as a popular open source library.
Chapter 10, Interacting with GraphQL APIs, begins by introducing the syntax for reading and writing data. The chapter covers how to interact with a GraphQL server with an HTTP library before moving on to using a popular purpose-built library.
Chapter 11, Unit Testing with Jest, covers how to test both pure functions and React components. A popular open source library is looked at to make tests less brittle when the internals of components are refactored. Some of the great features of Jest are stepped through, such as snapshot testing, mocking, and code coverage.
Answers, Contains the answers to all the exercises present in the chapters of this book.
You need to know the basics of JavaScript, including the following:
You need to know the basics of HTML, including the following:
An understanding of basic CSS is also helpful, but not essential:
You will need the following technologies installed on your computer:
npm install -g typescript
You can download the example code files for this book from your account at www.packt.com. If you purchased this book elsewhere, you can visit www.packt.com/support and register to have the files emailed directly to you.
You can download the code files by following these steps:
Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:
The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Learn-React-with-TypeScript-3. In case there's an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
We also provide a PDF file that has color images of the screenshots/diagrams used in this book. You can download it here: https://www.packtpub.com/sites/default/files/downloads/9781789610253_ColorImages.pdf.
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: "Let's create a new file called tsconfig.json in the root of our project."
A block of code is set as follows:
import * as React from "react";
const App: React.SFC = () => {
return <h1>My React App!</h1>;
};
When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:
interface IProps {
title: string;
content: string;
cancelCaption?: string;
okCaption?: string;
}
Any command-line input or output is written as follows:
cd my-components
npm install tslint tslint-react tslint-config-prettier --save-dev
Bold: Indicates a new term, an important word, or words that you see on screen. For example, words in menus or dialog boxes appear in the text like this. Here is an example: "We need to click the Install option to install the extension."
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, mention the book title in the subject of your message and email us at customercare@packtpub.com.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packt.com/submit-errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at copyright@packt.com with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in, and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!
For more information about Packt, please visit packt.com.
Facebook has become an incredibly popular app. As its popularity grew, so did the demand for new features. React was Facebook's answer to help more people work on the codebase and deliver features quicker. React worked so well for Facebook that they eventually open sourced it. Today, React is a mature library for building component-based frontends that is extremely popular and has a massive community and ecosystem.
TypeScript is also a popular, mature library maintained by a big company – namely, Microsoft. It allows users to add strong types to their JavaScript code, helping them to be more productive, particularly in large code bases.
This book will teach you how you can use both of these awesome libraries to build robust frontends that are easy to maintain. The first couple of chapters in the book focus solely on TypeScript. You'll then start to learn about React and how you can compose robust frontends using Typescript components with strong typing.
In this chapter, we'll cover TypeScript's relationship to JavaScript and the benefits it brings. A basic understanding of JavaScript is therefore required. We'll also cover the basics of TypeScript that you'll commonly use when writing code for the browser.
You'll come to understand the need to use TypeScript for building a frontend and the sort of projects for which TypeScript really shines. You will also see how to transpile your TypeScript code into JavaScript so that it can run in a browser. Last but not least, you'll learn how you can perform additional checks on your TypeScript code to make it readable and maintainable.
By the end of the chapter, you'll be ready to start learning how you can use TypeScript for building frontends with React.
In this chapter, we'll cover the following topics:
We will use the following technologies in this chapter:
npm install -g typescript
When a JavaScript codebase grows, it can become hard to read and maintain. TypeScript is an extension of JavaScript, adding static types. The TypeScript compiler reads in TypeScript code that includes type information and produces clean, readable JavaScript with the type information transformed and removed. The compiled code can then run in our favorite browsers and Node.js.
TypeScript offers several benefits over JavaScript:
We'll go through these points in detail in the following sections.
The type information helps the TypeScript compiler catch bugs and typos before our users run into them. In code editors such as Visual Studio Code, a mistake is underlined in red immediately after the user has gone wrong. As an example, create a file called utils.js and paste in the following code, which calculates the total price on an order line:
function calculateTotalPrice(product, quantity, discount) {
var priceWithoutDiscount = product.price * quantity;
var discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
There is a bug in the code that might be difficult for us to spot. If we open the file in Visual Studio Code, no errors are highlighted. If we change the extension of the file to .ts, Visual Studio Code immediately underlines bits of the code that need our attention in red:

Most of the errors are TypeScript asking for some type information. So, let's add some types to our code:
interface IProduct {
name: string;
unitPrice: number;
}
function calculateTotalPrice(product: IProduct, quantity: number, discount: number): number {
var priceWithoutDiscount: number = product.price * quantity;
var discountAmount: number = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
Don't worry if you don't understand what we just added; we'll go through types in the next section. The key point is that we now have a single error highlighted to us, which is, in fact, the bug:

The bug is that our function references a price property in the product object that doesn't exist. The property that we should reference is unitPrice.
Let's fix the bug in the previous section by renaming price to unitPrice. Notice how Visual Studio Code gives us IntelliSense lists unitPrice as an option because it looking at our type definition:

Here, TypeScript and Visual Studio Code are using the types to provide a better authoring experience for us. As well as IntelliSense, we are provided with code navigation features, and the safe renaming of functions and variables across multiple files. These features increase our productivity, particularly when the code base is large and there is a team of people working on it.
There is another benefit of TypeScript that is important to understand. TypeScript allows us to use some features in JavaScript that haven't yet been adopted by all browsers but still target those browsers. TypeScript achieves this by transpiling the use of these features down to JavaScript code that the targeted browser does support.
As an example, let's look at the exponentiation operator (**) in ES7, which isn't supported in IE. Let's create a file called future.ts and enter the following code:
var threeSquared: number = 3 ** 2;
console.log(threeSquared);
When we run the program in a browser, it should put 9 into the console. Before we do that, let's run the code against the TypeScript compiler to get the transpiled JavaScript. Run the following command in a terminal in the same directory as future.ts:
tsc future
This should generate a file called future.js with the following content:
var threeSquared = Math.pow(3, 2);
console.log(threeSquared);
So, TypeScript converted the exponentiation operator to a call to the Math.pow function, which is supported in IE. To confirm that this works, paste the generated JavaScript code into the console in IE and the output should be 9.
This example is purposely simple but probably not that useful. Async/await, spread operators, rest parameters, and arrow functions are far more useful features that IE doesn't support but TypeScript allows the use of. Don't worry if you don't know what the features in the last sentence are, as we'll cover them when we need them in the book.
We touched on types in the last section. In this section, we'll go through the basic types that are commonly used in TypeScript so that we start to understand what cases we should use in each type. We'll make heavy use of the online TypeScript playground, so be sure to have that ready.
Before understanding how we declare variables and functions with types in TypeScript, let's briefly look at primitive types, which are the most basic types. Primitive types are simple values that have no properties. TypeScript shares the following primitive types with JavaScript:
Types for JavaScript variables are determined at runtime. Types for JavaScript variables can also change at runtime. For example, a variable that holds a number can later be replaced by a string. Usually, this is unwanted behavior and can result in a bug in our app.
TypeScript annotations let us declare variables with specific types when we are writing our code. This allows the TypeScript compiler to check that the code adheres to these types before the code executes at runtime. In short, type annotations allow TypeScript to catch bugs where our code is using the wrong type much earlier than we would if we were writing our code in JavaScript.
TypeScript annotations let us declare variables with types using the :Type syntax.
let unitPrice: number;
var unitPrice;
unitPrice = "Table";
Notice that a red line appears under unitPrice, and if you hover over it, you are correctly informed that there is a type error:

function getTotal(unitPrice: number, quantity: number, discount: number): number {
const priceWithoutDiscount = unitPrice * quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
We've declared unitPrice, quantity, and discount parameters, all as numbers. The return type annotation comes after the function's parentheses, which is also a number in the preceding example.
let total: string = getTotal(500, "one", 0.1);
We find that one is underlined in red, highlighting that there is a type error:


The TypeScript compiler uses type annotations to check whether values assigned to variables and function parameters are valid for their type.
This strong type checking is something that we don't get in JavaScript, and it is very useful in large code bases because it helps us immediately detect type errors.
We have seen how type annotations are really valuable, but they involve a lot of extra typing. Luckily, TypeScript's powerful type inference system means we don't have to provide annotations all the time. We can use type inference when we immediately set a variable value.
Let's look at an example:
let flag = false;


So, when we declare a variable and immediately set its type, we can use type inference to save a few keystrokes.
What if we declare a variable with no type annotation and no value? What does TypeScript infer as the type? Let's enter the following code in the TypeScript playground and find out:
let flag;
If we hover our mouse over flag, we see it has been given the any type:

So, the TypeScript compiler gives a variable with no type annotation and no immediately assigned value, the any type. The any type is specific to TypeScript; it doesn't exist in JavaScript. It is a way of opting out of type checking on a particular variable. It is commonly used for dynamic content or values from third-party libraries. However, TypeScript's increasingly powerful type system means that we need to use any less often these days.
void is another type that doesn't exist in JavaScript. It is generally used to represent a non-returning function.
Let's look at an example:
function logText(text: string): void {
console.log(text);
}
The function simply logs some text into the console and doesn't return anything. So, we've marked the return type as void.

This saves us a few keystrokes while writing functions that don't return anything.
The never type represents something that would never occur and is typically used to specify unreachable areas of code. Again, this doesn't exist in JavaScript.
Time for an example:
function foreverTask(taskName: string): never {
while (true) {
console.log(`Doing ${taskName} over and over again ...`);
}
}
The function invokes an infinite loop and never returns, and so we have given it a type annotation of never. This is different to void because void means it will return, but with no value.
function foreverTask(taskName: string): never {
while (true) {
console.log(`Doing ${taskName} over and over again ...`);
break;
}
}
The TypeScript compiler quite rightly complains:


The never type is useful in places where the code never returns. However, we will probably need to explicitly define the never type annotation because the TypeScript compiler isn't smart enough yet to infer that.
Enumerations allow us to declare a meaningful set of friendly names that a variable can be set to. We use the enum keyword, followed by the name we want to give to it, followed by the possible values in curly braces.
Here's an example:
enum OrderStatus {
Paid,
Shipped,
Completed,
Cancelled
}
var OrderStatus;
(function (OrderStatus) {
OrderStatus[OrderStatus["Paid"] = 1] = "Paid";
OrderStatus[OrderStatus["Shipped"] = 2] = "Shipped";
OrderStatus[OrderStatus["Completed"] = 3] = "Completed";
OrderStatus[OrderStatus["Cancelled"] = 4] = "Cancelled";
})(OrderStatus || (OrderStatus = {}));
This is because enumerations don't exist in JavaScript, so the TypeScript compiler is transpiling the code into something that does exist.
let status = OrderStatus.Shipped;
Notice how we get nice IntelliSense when typing the value:

enum OrderStatus {
Paid = 1,
Shipped,
Completed,
Cancelled
}
let status = OrderStatus.Shipped;
console.log(status);
If we run the program, we should see 2 output in the console:

enum OrderStatus {
Paid = 1,
Shipped = 2,
Completed = 3,
Cancelled = 0
}
Enumerations are great for data such as a status that is stored as a specific set of integers but actually has some business meaning. They make our code more readable and less prone to error.
The object type is shared with JavaScript and represents a non-primitive type. Objects can contain typed properties to hold bits of information.
Let's work through an example:
const customer = {
name: "Lamps Ltd",
turnover: 2000134,
active: true
};
If we hover over name, turnover, and active, we'll see that TypeScript has smartly inferred the types to be string, number, and boolean respectively.

customer.turnover = 500000;
As we type the turnover property, IntelliSense provides the properties that are available on the object:


customer.profit = 10000;
We'll see that TypeScript complains:

This makes sense if we think about it. We've declared customer with name, turnover, and active properties, so setting a profit property should cause an error. If we wanted a profit property, we should have declared it in the original declaration.
In summary, the object type is flexible because we get to define any properties we require, but TypeScript will narrow down the type to prevent us incorrectly typing a property name.
Arrays are structures that TypeScript inherits from JavaScript. We add type annotations to arrays as usual, but with square brackets at the end to denote that this is an array type.
Let's take a look at an example:
const numbers: number[] = [];
Here, we have initialized the array as empty.
numbers.push(1);

const numbers = [1, 3, 5];
Let's take an example:
console.log(numbers[0]);
console.log(numbers[1]);
console.log(numbers[2]);
for (let i in numbers) {
console.log(numbers[i]);
}
If we run the program, we'll see 1, 3, and 5 output to the console again.
numbers.forEach(function (num) {
console.log(num);
});

Arrays are one of the most common types we'll use to structure our data. In the preceding examples, we've only used an array with elements having a number type, but any type can be used for elements, including objects, which in turn have their own properties.
In the Understanding basic types section, we introduced ourselves to objects, which are types that can have their own properties. Interfaces, type aliases, and classes are ways that we can define an object structure before we start using it.
Following here is the customer object we worked with, where we declared the customer variable with an initial object value:
const customer = {
name: "Lamps Ltd",
turnover: 2000134,
active: true
};
let customer: object;
customer = {
name: "Lamps Ltd",
turnover: 2000134,
active: true
};
customer.turnover = 2000200;

The TypeScript compiler doesn't know about the properties in the customer object and so thinks there's a problem.
So, we need another way of defining an object structure with the ability to set property values later in the program. That's where interfaces, type aliases, and classes come in; they let us define the structure of an object by letting us define our own types.
An interface is a contract that defines a type with a collection of property and method definitions without any implementation. Interfaces don't exist in JavaScript, so they are purely used by the TypeScript compiler to enforce the contract by type checking.
We create an interface with the interface keyword, followed by its name, followed by the bits that make up the interface in curly braces:
interface Product {
...
}
Properties are one of the elements that can be part of an interface. Properties can hold values associated with an object. So, when we define a property in an interface, we are saying that objects that implement the interface must have the property we have defined.
Let's start to play with an interface in the TypeScript playground:
interface Product {
name: string;
unitPrice: number;
}
const table: Product = {
name: "Table",
unitPrice: 500
}
const chair: Product = {
productName: "Table",
price: 70
}
As expected, we get a type error:

interface Product {
name: string;
unitPrice: number;
}
interface OrderDetail {
product: Product;
quantity: number;
}
const table: Product = {
name: "Table",
unitPrice: 500
}
const tableOrder: OrderDetail = {
product: table,
quantity: 1
};
This gives us the flexibility to create complex object structures, which is critical when writing large, complex apps.
Interfaces can contain method signatures as well. These won't contain the implementation of the method; they define the contracts for when interfaces are used in an implementation.
Let's look at an example:
interface OrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number;
}
Notice that the getTotal method on the interface doesn't specify anything about how the total is calculated – it just specifies the method signature that should be used.
const tableOrder: OrderDetail = {
product: table,
quantity: 1,
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice *
this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
};
Notice that the implemented method has the same signature as in the OrderDetail interface.
getTotal(discountPercentage: number): number {
const priceWithoutDiscount = this.product.unitPrice *
this.quantity;
const discountAmount = priceWithoutDiscount *
discountPercentage;
return priceWithoutDiscount - discountAmount;
}
total(discountPercentage: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discountPercentage;
return priceWithoutDiscount - discountAmount;
}

const tableOrder: OrderDetail = {
product: table,
quantity: 1,
getTotal(discountPercentage: number): string {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discountPercentage;
return (priceWithoutDiscount - discountAmount).toString();
}
};
This actually doesn't produce a compilation error in the TypeScript playground, but it should do!


The errors provided by TypeScript are fantastic—they are very specific about where the problem is, allowing us to quickly correct our mistakes.
interface OrderDetail {
...
getTotal(number): number;
}
However, omitting the parameter names arguably makes the interface harder to understand—how do we know exactly what the parameter is for?
We might want to make a property optional because not every situation where the interface is implemented requires it. Let's take the following steps in our OrderDetail interface:
interface OrderDetail {
product: Product;
quantity: number;
dateAdded?: Date,
getTotal(discount: number): number;
}
We'll see that our implementation of this interface, tableOrder, isn't broken. We can choose to add dateAdded to tableOrder but it isn't required.
interface OrderDetail {
product: Product;
quantity: number;
dateAdded?: Date,
getTotal(discount?: number): number;
}
getTotal(discount?: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * (discount || 0);
return priceWithoutDiscount - discountAmount;
}
We've also dealt with the case when a discount isn't passed into the method by using (discount || 0) in the discountAmount variable assignment.
tableOrder.getTotal()
The preceding line doesn't upset the TypeScript compiler.
We can stop a property from being changed after it has initially been set by using the readonly keyword before the property name.
interface Product {
readonly name: string;
unitPrice: number;
}
const table: Product = {
name: "Table",
unitPrice: 500
};
table.name = "Better Table";
As expected, we get a compilation error:

readonly properties are a simple way of freezing their values after being initially set. A common use case is when you want to code in a functional way and prevent unexpected mutations to a property.
Interfaces can extend other interfaces so that they inherit all the properties and methods from its parent. We do this using the extends keyword after the new interface name and before the interface name that is being extended.
Let's look at the following example:
interface Product {
name: string;
unitPrice: number;
}
interface DiscountCode {
code: string;
percentage: number;
}
interface ProductWithDiscountCodes extends Product {
discountCodes: DiscountCode[];
}
const table: ProductWithDiscountCodes = {
name: "Table",
unitPrice: 500,
discountCodes: [
{ code: "SUMMER10", percentage: 0.1 },
{ code: "BFRI", percentage: 0.2 }
]
};
Interfaces allow us to create complex but flexible structured types for our TypeScript program to use. They are a really important feature that we can use to create a robust, strongly-typed TypeScript program.
In simple terms, a type alias creates a new name for a type. To define a type alias, we use the type keyword, followed by the alias name, followed by the type that we want to alias.
We'll explore this with the following example:
type GetTotal = (discount: number) => number;
interface OrderDetail {
product: Product;
quantity: number;
getTotal: GetTotal;
}
Nothing changes with objects that implement this interface – it is purely a way we can structure our code. It arguably makes the code a little more readable.
type Product = {
name: string;
unitPrice: number;
};
type OrderDetail = {
product: Product;
quantity: number;
getTotal: (discount: number) => number;
};
const table: Product = {
name: "Table",
unitPrice: 500
};
const orderDetail: OrderDetail = {
product: table,
quantity: 1,
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
};
So, type aliases seem very similar to interfaces. What is the difference between a type alias and an interface? The main difference is that type aliases can't be extended or implemented from like you can with interfaces. So, for a simple structure that doesn't require inheritance, should we use an interface or should we use a type alias? There isn't strong reasoning to prefer either approach. However, we should be consistent with whichever approach we choose to improve the readability of our code.
Classes feature in many programming languages, including JavaScript. They let us shape objects with type annotations in a similar way to interfaces and type aliases. However, classes have many more features than interfaces and type aliases, which we'll explore in the following sections.
Classes have lots of features. So, in this section we'll look at the basic features of a class. We use the class keyword followed by the class name, followed by the definition of the class.
Let's look at this in more depth with the following example:
class Product {
name: string;
unitPrice: number;
}
const table = new Product();
table.name = "Table";
table.unitPrice = 500;
Notice that when we use this approach we don't need a type annotation for the table variable because the type can be inferred.
Classes have many more features than type aliases and interfaces though. One of these features is the ability to define the implementation of methods in a class.
Let's explore this with an example:
class OrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
const table = new Product();
table.name = "Table";
table.unitPrice = 500;
const orderDetail = new OrderDetail();
orderDetail.product = table;
orderDetail.quantity = 2;
const total = orderDetail.getTotal(0.1);
console.log(total);
If we run this and look at the console, we should see an output of 900.
We can use classes and interfaces together by defining the contract in an interface and then implementing the class as per the interface. We specify that a class is implementing a particular interface using the implements keyword.
As an example, we can define an interface for the order detail and then a class that implements this interface:
interface IOrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number;
}
class OrderDetail implements IOrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice *
this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
In the preceding example, we've prefixed the interface with I so that readers of the code can quickly see when we are referencing interfaces.
Why would we use this approach? It seems like more code than we need to write. So, what's the benefit? This approach allows us to have multiple implementations of an interface, which can be useful in certain situations.
Constructors are functions that perform the initialization of new instances of a class. In order to implement a constructor, we implement a function called constructor. It's common to set property values in the constructor to simplify consumption of the class.
Let's look at the following example:
class OrderDetail implements IOrderDetail {
product: Product;
quantity: number;
constructor(product: Product, quantity: number) {
this.product = product;
this.quantity = quantity;
}
getTotal(discount: number): number {
...
}
}
const orderDetail = new OrderDetail(table, 2);
constructor(product: Product, quantity: number = 1) {
this.product = product;
this.quantity = quantity;
}
const orderDetail = new OrderDetail(table);
class OrderDetail implements IOrderDetail {
constructor(public product: Product, public quantity: number = 1) {
this.product = product;
this.quantity = quantity;
}
getTotal(discount: number): number {
...
}
}
Classes can extend other classes. This is the same concept as interfaces extending other interfaces, which we covered in the Extending interfaces section. This is a way for class properties and methods to be shared with child classes.
As with interfaces, we use the extends keyword followed by the class we are extending. Let's look at an example:
class Product {
name: string;
unitPrice: number;
}
interface DiscountCode {
code: string;
percentage: number;
}
class ProductWithDiscountCodes extends Product {
discountCodes: DiscountCode[];
}
const table = new ProductWithDiscountCodes();
table.name = "Table";
table.unitPrice = 500;
table.discountCodes = [
{ code: "SUMMER10", percentage: 0.1 },
{ code: "BFRI", percentage: 0.2 }
];
class Product {
constructor(public name: string, public unitPrice: number) {
}
}
interface DiscountCode {
code: string;
percentage: number;
}
class ProductWithDiscountCodes extends Product {
constructor(public name: string, public unitPrice: number) {
super(name, unitPrice);
}
discountCodes: DiscountCode[];
}
Abstract classes are a special type of class that can only be inherited from and not instantiated. They are declared with the abstract keyword, as in the following example:
abstract class Product {
name: string;
unitPrice: number;
}

class Food extends Product {
constructor(public bestBefore: Date) {
super();
}
}
const bread = new Food(new Date(2019, 6, 1));
Abstract classes can have abstract methods that child classes must implement. Abstract methods are declared with the abstract keyword in front of them, as in the following example:
abstract class Product {
name: string;
unitPrice: number;
abstract delete(): void;
}

class Food extends Product {
deleted: boolean;
constructor(public bestBefore: Date) {
super();
}
delete() {
this.deleted = false;
}
}
So far, all our class properties and methods have automatically had the public access modifier. This means they are available to interact with class instances and child classes. We can explicitly set the public keyword on our class properties and methods immediately before the property or method name:
class OrderDetail {
public product: Product;
public quantity: number;
public getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
As you might have guessed, there is another access modifier, called private, which allows the member to only be available to interact with inside the class and not on class instances or child classes.
Let's look at an example:
class OrderDetail {
public product: Product;
public quantity: number;
private deleted: boolean;
public delete(): void {
this.deleted = true;
}
...
}
const orderDetail = new OrderDetail();
orderDetail.deleted = true;
As expected, the compiler complains:

There is a third access modifier, protected, which allows the member to be available to interact with inside the class and on child classes, but not on class instances.
Our classes so far have had simple property declarations. However, for more complex scenarios, we can implement a property with a getter and a setter. When implementing getters and setters, generally, you'll need a private property to hold the property value:
Let's take a look at an example:
class Product {
name: string;
private _unitPrice: number;
get unitPrice(): number {
return this._unitPrice || 0;
}
set unitPrice(value: number) {
if (value < 0) {
value = 0;
}
this._unitPrice = value;
}
}
const table = new Product();
table.name = "Table";
console.log(table.unitPrice);
table.unitPrice = -10;
console.log(table.unitPrice);
If we run this, we should see two 0's in the console.
Static properties and methods are held in the class itself and not in class instances. They can be declared using the static keyword before the property or method name.
Let's look at the following example:
class OrderDetail {
product: Product;
quantity: number;
static getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}

static getTotal(unitPrice: number, quantity: number, discount: number): number {
const priceWithoutDiscount = unitPrice * quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
const total = OrderDetail.getTotal(500, 2, 0.1);
console.log(total);
If we run the preceding program, we should get an output of 900 in the console.
By default, TypeScript generated JavaScript code that executes in what is called the global scope. This means code from one file is automatically available in another file. This in turn means that the functions we implement can overwrite functions in other files if the names are the same, which can cause our applications to break.
Let's look at an example in Visual Studio Code:
interface Product {
name: string;
unitPrice: number;
}
class OrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
The compiler doesn't give us any complaints. In particular, the reference to the Product interface in the OrderDetail class is able to be resolved, even though it's in a different file. This is because both Product and OrderDetail are in the global scope.
Operating in the global scope is problematic because item names can conflict across different files, and as our code base grows, this is harder to avoid. Modules resolve this issue and help us write well organized and reusable code.
Modules feature in JavaScript as part of ES6, which is great. However, lots of code exists in other popular module formats that came before this standardization. TypeScript allows us to write our code using ES6 modules, which can then transpile into another module format if specified.
Here is a brief description of the different module formats that TypeScript can transpile to:
In the following sections (and, in fact, this whole book), we'll write our code using ES6 modules.
Exporting code from a module allows it to be used by other modules. In order to export from a module, we use the export keyword. We can specify that an item is exported using export directly before its definition. Exports can be applied to interfaces, type aliases, classes, functions, constants, and so on.
Let's start to adjust our example code from the previous section to operate in modules rather than the global scope:
export interface Product {
name: string;
unitPrice: number;
}

This is because Product is no longer in the global scope but OrderDetail still is. We'll resolve this in the next section, but let's look at alternative ways we can export the Product interface first.
interface Product {
name: string;
unitPrice: number;
}
export { Product }
interface Product {
name: string;
unitPrice: number;
}
export { Product as Stock }
Importing allows us to import items from an exported module. We do this using an import statement that includes the item names to import in curly braces and the file path to get the items from (excluding the ts extension). We can only import items that are exported in the other module file.
import { Product } from "./product";
class OrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
import { Product as Stock } from "./product";
class OrderDetail {
product: Stock;
quantity: number;
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
We can specify a single item that can be exported by default using the default keyword:
export default interface {
name: string;
unitPrice: number;
}
Notice that we don't need to name the interface. We can then import a default exported item using an import statement without the curly braces with a name of our choice:
import Product from "./product";
We need to compile our TypeScript code before it can be executed in a browser. We do this by running the TypeScript compiler, tsc, on the files we want to compile. TypeScript is very popular and is used in many different situations:
All these situations involve slightly different requirements for the TypeScript compiler. So, the compiler gives us lots of different options to hopefully meet the requirements of our particular situation.
export interface Product {
name: string;
unitPrice: number;
}
export class OrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
tsc orderDetail
"use strict";
exports.__esModule = true;
var OrderDetail = (function () {
function OrderDetail() {
}
OrderDetail.prototype.getTotal = function (discount) {
var priceWithoutDiscount = this.product.unitPrice * this.quantity;
var discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
};
return OrderDetail;
}());
exports.OrderDetail = OrderDetail;
We'll continue to use orderDetail.ts in the following sections as we explore how the compiler can be configured.
As mentioned earlier, there are lots of configuration options for the TypeScript compiler. All the configuration options can be found at https://www.typescriptlang.org/docs/handbook/compiler-options.html. The following sections detail some of the more common options that are used.
This determines the ECMAScript version the transpiled code will be generated in.
The default is ES3, which will ensure the code works in a wide range of browsers and their different versions. However, this compilation target will generate the most amount of code because the compiler will generate polyfill code for features that aren't supported in ES3.
The ESNext option is the other extreme, which compiles to the latest supported proposed ES features. This will generate the least amount of code, but will only work on browsers that have implemented the features we have used.
As an example, let's compile orderDetail.ts targeting ES6 browsers. Enter the following in the terminal:
tsc orderDetail --target es6
Our transpiled JavaScript will be very different from the last compilation and much closer to our source TypeScript because classes are supported in es6:
export class OrderDetail {
getTotal(discount) {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
By default, the transpiled JavaScript files are created in the same directory as the TypeScript files. --outDir can be used to place these files in a different directory.
Let's give this a try and output the transpiled orderDetail.js to a folder called dist. Let's enter the following in the terminal:
tsc orderDetail --outDir dist
A dist folder will be created containing the generated orderDetail.js file.
This specifies the module format that the generated JavaScript should use. The default is the CommonJS module format if ES3 or ES5 are targeted. ES6 and ESNext are common options today when creating a new project.
This option tells the TypeScript compiler to process JavaScript files as well as TypeScript files. This is useful if we've written some of our code in JavaScript and used features that haven't been implemented yet in all browsers. In this situation, we can use the TypeScript compiler to transpile our JavaScript into something that will work with a wider range of browsers.
This option makes the TypeScript compiler run indefinitely. Whenever a source file is changed, the compiling process is triggered automatically to generate the new version. This is a useful option to switch on during our developments:
tsc orderDetail --watch
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * (discount || 0);
return priceWithoutDiscount - discountAmount;
}
To exit the watch mode, we can kill the terminal by clicking the bin icon in the Terminal.
This forces us to explicitly specify the any type where we want to use it. This forces us to think about our use of any and whether we really need it.
Let's explore this with an example:
export class OrderDetail {
...
doSomething(input) {
input.something();
return input.result;
}
}
tsc orderDetail --noImplicitAny
The compiler outputs the following error message because we haven't explicitly said what type the input parameter is:
orderDetail.ts(14,15): error TS7006: Parameter 'input' implicitly has an 'any' type.
doSomething(input: {something: () => void, result: string}) {
input.something();
return input.result;
}
If we do a compilation with --noImplicitAny again, the compiler is happy.
This ensures we return a value in all branches of a function if the return type isn't void.
Let's see this in action with an example:
getTotal(discount: number): number {
if (discount) {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
} else {
// We forgot about this branch!
}
}
tsc orderDetail
tsc orderDetail --noImplicitReturns
We get the following error, as expected:
orderDetail.ts(9,31): error TS7030: Not all code paths return a value.
When this is set, *.map files are generated during the transpilation process. This will allow us to debug the TypeScript version of the program (rather than the transpiled JavaScript). So, this is generally switched on during development.
This tells the TypeScript compiler how to resolve modules. This can be set to classic or node. If we are using ES6 modules, this defaults to classic, which means the TypeScript compiler struggles to find third-party packages such as Axios. So, we can explicitly set this to node to tell the compiler to look for modules in "node_modules".
As we have seen, there are lots of different switches that we can apply to the compilation process, and repeatedly specifying these on the command line is a little clunky. Luckily, we can specify these options in a file called tsconfig.json. The compiler options we have looked at in previous sections are defined in a compilerOptions field without the "--" prefix.
Let's take a look at an example:
{
"compilerOptions": {
"target": "esnext",
"outDir": "dist",
"module": "es6",
"moduleResolution": "node",
"sourceMap": true,
"noImplicitReturns": true,
"noImplicitAny": true
}
}
tsc
The compilation will run fine, with the transpiled JavaScript being output to the dist folder along with a source map file.
There are several ways to tell the TypeScript compiler which files to process. The simplest method is to explicitly list the files in the files field:
{
"compilerOptions": {
...
},
"files": ["product.ts", "orderDetail.ts"]
}
However, that approach is difficult to maintain as our code base grows. A more maintainable approach is to define file patterns for what to include and exclude with the include and exclude fields.
The following example looks at the use of these fields:
{
"compilerOptions": {
...
},
"include": ["src/**/*"]
}
tsc
Let's create an src folder and move orderDetail.ts into this folder. If we do a compile again, it will successfully find the files and do a compilation.
So, we have lots of options for adapting the TypeScript compiler to our particular situation. Some options, such as --noImplicitAny, force us to write good TypeScript code. We can take the checks on our code to the next level by introducing linting into our project, which we'll look at in the next section.
As we have seen, the compiler does lots of useful checks against our TypeScript code to help us write error-free code. We can take this a step further and lint the code to help us make our code even more readable and maintainable. TSLint is a linter that is very popular in TypeScript projects, and we will explore it in this section.
The home page for TSLint is at https://palantir.github.io/tslint/.
We'll install TSLint in the next section.
We'll install TSLint in this section, along with a Visual Studio Code extension that will highlight linting problems right in the code:
npm install -g tslint

Now that this extension is installed, along with TSLint globally, linting errors will be highlighted right in our code, as we'll see in the following sections.
The rules that tslint uses when checking our code are configurable in a file called tslint.json. In order to explore some of the rules, we first need a TypeScript file:
export interface Product {
name: string;
unitPrice: number;
}
export class OrderDetail {
product: Product;
quantity: number;
getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
{
"rules": {
"member-access": true
}
}

export class OrderDetail {
public product: Product;
public quantity: number;
public getTotal(discount: number): number {
const priceWithoutDiscount = this.product.unitPrice * this.quantity;
const discountAmount = priceWithoutDiscount * discount;
return priceWithoutDiscount - discountAmount;
}
}
The member-access rule forces us to write more code – how can this be a good thing? The rule is useful if you're reading the code and don't know TypeScript well enough to understand that class members without access modifiers are public. So, it's great if our team consists of developers who don't know TypeScript that well yet, but not necessarily for an experienced team of TypeScript developers.
Lots of the tslint rules are like member-access – in some teams, they will work well and in others, they don't really add value. This is why rules are configurable!
tslint has a handy collection of built-in rulesets that can be used. We can use these by specifying the ruleset name in the extends field. We can use multiple rulesets by putting all their names in the array:
{
"extends": ["tslint:recommended"]
}
We immediately get lint errors when tslint.json is saved. The error is complaining about the lack of an I prefix on our Product interface. The logic behind the rule is that, while reading code, if a type starts with an I, we immediately know that it is an interface.
{
"extends": ["tslint:recommended"],
"rules": {
"interface-name": false
}
}
When tslint.json is saved, the linting errors immediately go away.
We can exclude files from the linting process. This is useful for excluding third-party code. We do this by specifying an array of files in an exclude field in the linterOptions field:
{
"extends": ["tslint:recommended"],
"linterOptions": {
"exclude": ["node_modules/**/*.ts"]
}
}
The preceding configuration excludes third-party node packages from the linting process.
Now that we've added TSLint to our tool belt, we are going to add another tool that will automatically format our code for us. This will help our code adhere to some of the code formattings TSLint rules.
In this section, we are going to install another extension in Visual Studio Code, called Prettier, which will automatically format our code. As well as putting a stop to all the ongoing debates over styles, it will help us adhere to some of the TSLint rules:


Now that this extension is installed, when we save our TypeScript code, it will automatically be formatted nicely for us.
At the start of this chapter, there was a section on why we would use TypeScript to build a frontend. We now have first-hand experience of TypeScript catching errors early and giving us productivity features such as IntelliSense. We learned that TypeScript is just an extension of JavaScript. So, we get to use all of the features in JavaScript plus additional stuff from TypeScript. One of these additional things is type annotations, which help the compiler spot errors and light up features such as code navigation in our code editor.
We haven't covered everything about types yet, but we have enough knowledge to build fairly complex TypeScript programs now. Classes, in particular, allow us to model complex real-world objects nicely. We learned about modules and how they keep us out of that dangerous global scope. Modules allow us to structure code nicely and make it reusable. We can even use these if we need to support IE, because of that magical TypeScript compiler.
We learned a fair bit about the TypeScript compiler and how it can work well in different use cases because it is very configurable. This is going to be important for when we start to use TypeScript with React later in the book.
TSLint and Prettier were the icings on the cake. It's down to us and our team to debate and decide the TSLint rules we should go with. The benefit of both these tools is that they force consistency across our code base, which makes it more readable.
Now that we understand the basics of TypeScript, we'll dive into the new features that have been added in TypeScript 3.
Here are some questions to test what you have learned in this first chapter. The answers can be found in the appendix.
Good luck!
const flag = false;
class Product {
constructor(public name: string, public unitPrice: number) {}
}
let table = new Product();
table.name = "Table";
table.unitPrice = 700;
http://www.typescriptlang.org has great documentation on TypeScript. It is worth looking at the following pages of this site to cement your knowledge, or using them as a quick reference guide:
The full list of tslint rules can be found at https://palantir.github.io/tslint/rules/.
In its six years of existence, TypeScript has continued to move forward and mature nicely. Is TypeScript 3 a significant release for React developers? What exactly are the new features that we have to add to our toolkit in TypeScript 3? These questions will be answered in this chapter, starting with the tuple type and how it can now be successfully used with the rest and spread JavaScript syntax, which is very popular in the React community. We'll then move on to the new unknown type and how it can be used as an alternative to the any type. Further more, we'll break TypeScript projects up into smaller projects with the new project references in TypeScript. Finally, we'll go about defining default properties in a strongly-typed React component that has improved in TypeScript 3.
By the end of the chapter, we'll be ready to start learning how you can use TypeScript 3 to build frontends with React. In this chapter, we'll cover the following topics:
In this chapter, we will use the same technologies as in Chapter 1, TypeScript Basics:
npm install -g typescript
tsc -v
If you need to upgrade to the latest version, you can run the following command:
npm install -g typescript@latest
Tuples have had a few enhancements in TypeScript 3, so that they can be used with the popular rest and spread JavaScript syntax. Before we get into the specific enhancements, we'll go through what tuples are, along with what the rest and spread syntax is. A tuple is like an array but the number of elements are fixed. It's a simple way to structure data and use some type safety.
Let's have a play with tuples:
let product: [string, number];
We've initialized a product variable to a tuple type with two elements. The first element is a string and the second a number.
product = ["Table", 500];
product = [500, "Table"];
Not surprisingly, we get a compilation error. If we hover over 500, the compiler quite rightly complains that it was expecting a string. If we hover over "Table", the compiler complains that it expects a number:

So, we do get type safety, but tuples tell us nothing about what should be in the elements. So, they are nice for small structures or structures where the elements are obvious.
let flag: [string, boolean];
flag = ["Active", false]
let last3Scores: [string, number, number, number]
last3Scores = ["Billy", 60, 70, 75];
let point: [number, number, number];
point = [100, 200, 100];
let customer: [string, number, number];
customer = ["Tables Ltd", 500100, 10500];
What exactly do those last two numbers represent?
let product: [string, number];
product = ["Table", 500];
console.log(product[0]);
console.log(product[1]);
If we run the program, we'll get "Table" and 500 output to the console.
let product: [string, number];
product = ["Table", 500];
for (let element in product) {
console.log(product[element]);
}
product.forEach(function(element) {
console.log(element);
});
Running the program, will output Table and 500 to the console twice. Notice that we don't need to add a type annotation to the element variable because the TypeScript compiler cleverly infers this.
So, that's the tuple type, but's what's new in TypeScript 3? The enhancements have been largely driven by the popularity of JavaScript's rest and spread syntax, so let's briefly cover this in the next section.
In JavaScript, a rest parameter collects multiple arguments and condenses them into a single argument. It is called rest because it collects the rest of the arguments into a single argument.
This syntax was introduced in ES6 and allows us to nicely implement functions that have an indefinite number of parameters.
We define a rest parameter with three dots preceding the parameter name.
Let's go through a quick example:
function logScores(...scores) {
console.log(scores);
}
logScores(50, 85, 75);
If we run this, we'll get an array of the three elements we passed in as parameters output to the console. So, our scores parameter has collected all the arguments into an array.
The spread syntax is the opposite of rest parameters. It allows an iterable, such as array, to be expanded into function arguments.
Let's look at an example:
function logScore(score1, score2, score3) {
console.log(score1, score2, score3);
}
Note that this is still pure JavaScript – no types just yet!
const scores = [75, 65, 80];
logScore(...scores);
If you are using the TypeScript playground, you'll get the compilation error, expected 3 arguments, but got 0 or more. The program still runs though, because this is perfectly valid JavaScript. 75, 65, 80 will be output to the console if we do run it.
In the following sections, we'll see how the new features in TypeScript 3 help us help the compiler to better understand what we are trying to do when using rest and spread. This will allow us to resolve the compilation errors seen in the preceding example.
Before TypeScript 3, tuples had to have a fixed amount of elements. TypeScript 3 gives us a little more flexibility with rest elements. rest elements are similar to rest parameters, described in the last section, but they work with tuple element types. A rest element allows us to define an open-ended tuple.
Time to go through an example:
type Scores = [string, ...number[]];
const billyScores: Scores = ["Billy", 60, 70, 75];
const sallyScores: Scores = ["Sally", 60, 70, 75, 70];
Both these variables compile fine, as we would expect, because we have defined the numbers as open-ended.
Tuple function parameters in TypeScript 3 allow us to create strongly-typed rest parameters.
Time for an example:
function logScores(...scores) {
console.log(scores);
}
function logScores(...scores: [...number[]]) {
console.log(scores);
}
logScores(50, 85, 75);
We don't get a compiler error, and if we run the program, we get an array containing 50, 85, 75 output in the console.
We can create an enhanced version of our function that uses the Scores type from the Open-ended tuples section.
type Scores = [string, ...number[]];
function logNameAndScores(...scores: Scores) {
console.log(scores);
}
logNameAndScores("Sally", 60, 70, 75, 70);
If we run the program, Sally and her array of scores will be output to the console.
TypeScript 3 allows us to use tuples with spread expressions.
Let's look at an example:
function logScore(score1, score2, score3) {
console.log(score1 + ", " + score2 + ", " + score3);
}
const scores = [75, 65, 80];
logScore(...scores);
The TypeScript compiler raised the error Expected 3 arguments, but got 0 or more.
function logScore(score1: number, score2: number, score3: number) {
console.log(score1, score2, score3);
}
There's nothing new yet, and we're still getting the compilation error.
const scores: [number, number, number] = [75, 65, 80];
That's it – the compilation error has gone! All we needed to do was tell the compiler how many items were in scores for it to successfully spread into the logScore function.
So, in TypeScript 3, we can spread into fixed tuples. What about open-ended tuples? Let's give that a try:
const scoresUnlimited: [...number[]] = [75, 65, 80];
logScore(...scoresUnlimited);
Unfortunately, the compiler is not yet quite clever enough to let us do this. We get the compilation error Expected 3 arguments, but got 0 or more.:

In TypeScript 3, we can now define an empty tuple type. Let's have a little play with this in the TypeScript playground:
type Empty = [];
const empty: Empty = [];
const notEmpty: Empty = ["Billy"];
As expected, we get a compilation error:

Why is an empty tuple type useful, though? On its own, it perhaps is not that useful, but it can be used as part of a union type, which we'll cover in detail later in the book. As a quick example for now, we can create a type for no more than three scores, where no scores is also acceptable:
type Scores = [] | [number] | [number, number] | [number, number, number]
const benScores: Scores = [];
const samScores: Scores = [55];
const bobScores: Scores = [95, 75];
const jayneScores: Scores = [65, 50, 70];
const sarahScores: Scores = [95, 50, 75, 75];
All the scores are valid except Sarah's, because four scores aren't allowed in the Scores type.
The final tuple enhancement in TypeScript 3 is the ability to have optional elements. Optional elements are specified using a ? at the end of the element type.
Let's look at another example using our scores theme:
type Scores = [number, number?, number?];
const samScores: Scores = [55];
const bobScores: Scores = [95, 75];
const jayneScores: Scores = [65, 50, 70];
As expected, this compiles just fine.
const sarahScores: Scores = [95, 50, 75, 75];
We get a compilation error, as we would expect:

const benScores: Scores = [];
When defining optional elements in a tuple, they are restricted to the end of the tuple. Let's try to define a required element after an optional element:
type ProblematicScores = [number?, number?, number];
We get a compilation error, as expected:

Optional elements also work in a function rest parameter. Let's try this:
type Scores = [number, number?, number?];
function logScores(...scores: Scores) {
console.log(scores);
}
logScores(45, 80);
logScores(45, 70, 80, 65);
When we have optional parameters, it is likely our function's implementation will need to know which arguments have been passed. We can use the tuple's length property to do this:
type Scores = [number, number?, number?];
function logScoresEnhanced(...scores: Scores) {
if (scores.length === 3) {
console.log(scores, "Thank you for logging all 3 scores");
} else {
console.log(scores);
}
}
logScoresEnhanced(60, 70, 75);
logScoresEnhanced(45, 80);
logScoresEnhanced(95);
If we run the program, we only get thanked after the first call when we pass all three scores.
All the enhancements to tuples in TypeScript 3 allow us to use the rest and spread syntax in a strongly-typed fashion. We'll make use of this feature later in the book, when we work with React components.
unknown is a new type that has been added in TypeScript 3. Before TypeScript 3, we may have used the any type when we weren't sure of all the properties and methods in an object from a third-party library. However, when we declare a variable with the any type, the TypeScript compiler won't do any type checking on it. The unknown type can be used in these situations to make our code more type-safe. This is because unknown types are type-checked. So, unknown can often be used as an alternative to any.
In the TypeScript playground, let's go through an example of a function using any and an improved version using unknown:
function logScores(scores: any) {
console.log(scores.firstName);
console.log(scores.scores);
}
logScores({
name: "Billy",
scores: [60, 70, 75]
});
If we run the program, we get undefined followed by [60, 70, 75] in the console. We passed in a correct object parameter, but our function logs firstName instead of name to the console. The program compiled just fine and didn't produce an error at runtime, but didn't give the result we wanted. This is all because we told the compiler not to check any types with the any type.
function logScoresBetter(scores: unknown) {
console.log(scores.firstName);
console.log(scores.scores);
}
We immediately get compiler warnings where we reference the properties in scores:

So, the compiler is checking our scores variable now, which is great, and is even warning us about the firstName property. However, the scores property is also giving a complication error but is valid. So, how do we tell the compiler this? We need to explicitly do some type checking ourselves in our code. We'll cover a couple of ways of doing this in the following sections.
One way we can perform type checking in a function is with another function that has a return type as a type predicate. Let's explore this and eventually create a new version of our logScores function:
const scoresCheck = (
scores: any
): scores is { name: string; scores: number[] } => {
return "name" in scores && "scores" in scores;
};
This takes in a scores parameter that has a type predicate, scores is { name: string; scores: number[] }, ensuring it contains the correctly typed name and scores properties. The function simply returns whether the scores parameter contains the name and scores properties.
function logScores(scores: unknown) {
if (scoresCheck(scores)) {
console.log(scores.firstName);
console.log(scores.scores);
}
}
We immediately get the compilation error we want:

The type predicate, scores is { name: string, scores: number[] }, allows the TypeScript compiler to narrow down the type in the if block that logs the properties to the console. This results in scores.scores compiling fine, but scores.firstName is giving an error, which is just what we want.
The type predicate is the key bit. Without it, the TypeScript compiler will still throw errors on the valid scores.scores reference. Try removing the type predicate and see for yourself.
Note that we can make the predicate a little more readable with a type alias:
type Scores = { name: string; scores: number[] }
const scoresCheck = (
scores: any
): scores is Scores => {
return "name" in scores && "scores" in scores;
};
Using a type predicate in this way is called a type guard. There are other ways of implementing type guards, which we'll cover later in the book.
The other way of performing type checking we are going to look at when using unknown is to use type assertion. Type assertion lets us tell the compiler what the type is with the as keyword.
Let's create yet another version of our logScores function as an example:
type Scores = {
name: string;
scores: number[]
};
function logScores(scores: unknown) {
console.log((scores as Scores).firstName);
console.log((scores as Scores).scores);
}
That's enough information for the compiler to pinpoint the problem:

The unknown type allows us to reduce our use of the any type and create more strongly-typed and robust TypeScript programs. We do have to write more code, though, when referencing unknown types. The additional code we need to write needs to check the type of the unknown variable so that the TypeScript compiler can be sure we are accessing valid members within it.
TypeScript 3 allows TypeScript projects to depend on other TypeScript projects by allowing tsconfig.json to reference other tsconfig.json files.
This makes it easier to split our code up into smaller projects. Our frontend code might be in TypeScript, in addition to having our backend in TypeScript. With TypeScript 3, we can have a frontend TypeScript project, a backend TypeScript project, and a shared TypeScript project that contains code that is used in both the frontend and backend. Splitting our code up into smaller projects can also can give us faster builds, because they can work incrementally.
In order to explore this, we are going to work through an example of a TypeScript project referencing another project in Visual Studio Code:
{
"compilerOptions": {
"target": "es5",
"outDir": "dist",
"module": "es6",
"sourceMap": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"rootDir": "src"
},
"include": ["src/**/*"]
}
export function randomString() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16);
}
This is a function that creates a random string of characters, as the name suggests. We are going to use this function in another project.
{
"compilerOptions": {
"target": "es5",
"outDir": "dist",
"module": "es6",
"sourceMap": true,
"noImplicitReturns": true,
"noImplicitAny": true
},
"include": ["src/**/*"]
}
import { randomString } from "../../Shared/dist/utils";
class Person {
id: string;
name: string;
constructor() {
this.id = randomString();
}
}
The code defines a simple class of information about a person. The unique identifier of the person is set to a random string in the constructor using the randomString function from our Shared project.
cd Shared
tsc
The Shared project compiles just fine.
cd ..
cd ProjectA
tsc
We get a compilation error:
error TS7016: Could not find a declaration file for module '../../Shared/dist/utils'. '.../Shared/dist/utils.js' implicitly has an 'any' type.
So, we created two dependent projects, but they don't properly understand each other yet, which is why we are getting the error. We'll resolve this in the following sections, using TypeScript 3's new features for multiple projects.
The first step in setting up TypeScript 3's multiple projects feature is to reference projects using a new field called references in tsconfig.json. This field is an array of objects that specify projects to reference.
In our working example, let's make ProjectA start to understand the Shared project:
{
"compilerOptions": {
...
},
"references": [
{ "path": "../shared" }
]
}
"references": [
{ "path": "../shared", "prepend": true }
]
We're not going to use prepend in our example though.
error TS6306: Referenced project '.../shared' must have setting "composite": true
The error gives a great clue as to what is wrong. We'll resolve this problem with the missing composite setting in the next section.
Just referencing another project isn't enough for the TypeScript compiler to properly handle multiple projects. We need to add some additional compiler options in the dependent project.
The compilerOptions field has a new field called composite, which must be set to true if we are using multiple projects. This ensures certain options are enabled so that this project can be referenced and built incrementally for any project that depends on it.
When composite is true, declaration must also be set to true, forcing the corresponding .d.ts file to be generated, containing the project's types. This allows TypeScript to only build dependent projects when types are changed and not rebuild all the dependent projects all the time.
Let's make the following changes to our working example:
{
"compilerOptions": {
"composite": true,
"declaration": true,
...
},
}
cd ..
cd Shared
tsc
The project compiles okay. Let's now try to compile ProjectA again in the terminal:
cd ..
cd ProjectA
tsc
This time, ProjectA compiles just fine.
So, we have successfully tied together two projects using TypeScript 3's multiple projects feature. In the next section, we'll improve the setup of our projects even more.
In order for the Go to Definition feature in Visual Studio Code to work across projects, we need to set the declarationMap setting in tsconfig.json.
Let's continue with our multiple project example:

We are taken to the declaration file rather than the source file:

{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
...
},
}
If we compile the Shared project and try the Go to Definition feature again, we are taken to the source file, as we would want.
So, by setting declarationMap to true in the dependent project, along with composite and declaration, we get great support for multiple TypeScript projects.
The TypeScript 3 compiler includes the ability to perform smart incremental builds using the --build flag. Let's give this a try in our example multiple project solution:
tsc --build ProjectA --verbose
The --verbose flag tells the compiler to tell us the details of what it's doing. The messages confirm to us that it has picked up the Shared project as well as ProjectA:
Projects in this build:
* Shared/tsconfig.json
* ProjectA/tsconfig.json
The compiler then checks each project to see if it's up to date. If the project is up to date, we get something like the following:
Project 'Shared/tsconfig.json' is up to date because newest input 'Shared/src/utils.ts' is older than oldest output 'Shared/dist/utils.js'
tsc --build ProjectA --verbose
As expected, we get a message to indicate that the Shared project is out of date and will be rebuilt:
Project 'Shared/tsconfig.json' is out of date because oldest
output 'Shared/dist/utils.js' is older than newest input 'Shared/src/utils.ts
Building project '.../Shared/tsconfig.json'
tsc --build ProjectA --force --verbose
When we do this, the compiler will still check whether projects are up to date (and tell us), but then it goes on to build each project.
So, in addition to great multiple-project support, we can speed up solution builds using the --build flag. As the solution grows over time, this becomes increasingly valuable. If ever we want to force a rebuild of a project, we can use the --force flag along with --build.
TypeScript 3 has also improved how we can set default properties on React components with --strictNullChecks. Before TypeScript 3, we had to set properties that had default values as optional and perform null checks when referencing them. We haven't introduced React yet in this book, so we'll only touch on this briefly at this point.
Let's look through an example to get a feel for the improvement:
interface IProps {
text: string;
delimiter?: string;
}
class SplitText extends Component<IProps> {
static defaultProps = {
delimiter: ","
};
render() {
const bits = this.props.text.split(this.props.delimiter!);
return (
<ul>
{bits.map((bit: string) => (
<li key={bit}>{bit}</li>
))}
</ul>
);
}
}
const App = () => (
<div>
<SplitText text="Fred,Jane,Bob" />
</div>
);
export default App;
The component has a delimiter property that defaults to ",". In TypeScript 2.9, we need to make delimiter an optional property, otherwise we get a compiler error if we don't specify it in the calling component (even though there is a default).
Also notice that we need to put a ! after we reference delimiter in the bits variable declaration. This is to tell the compiler that this will never be undefined.
const App = () => (
<div>
<SplitText text="Fred,Jane,Bob" />
</div>
);
Here's what it looks like when rendered:

interface IProps {
text: string;
delimiter: string;
}
class SplitText extends React.Component<IProps> {
static defaultProps = {
delimiter: ","
};
render() {
const bits = this.props.text.split(this.props.delimiter);
return (
<ul>
{bits.map((bit: string) => (
<li key={bit}>{bit}</li>
))}
</ul>
);
}
}
Notice that we didn't need to make the delimiter property optional. Also notice that we didn't need to tell the compiler that this.props.delimiter can't be undefined.
So, in summary, we don't have to fiddle around to make default properties work nicely in TypeScript 3!
This is our first taste of React. Don't worry if the code examples don't make much sense at this point. We'll start to learn about React components in Chapter 3, Getting Started with React and TypeScript.
Using the rest and spread syntax is very common nowadays, particularly when building React apps. We've seen how TypeScript 3, with the enhancement of tuples, allows us to use rest and spread in a strongly-typed fashion.
We've also seen how we can use the unknown type to reduce our use of the any type. The unknown type does require us to write more code, but it also allows us to create a more strongly-typed, more maintainable code base.
TypeScript has always made working with large code bases easier. With the introduction of project references, we can now split our solution into smaller projects more easily. This approach makes large solutions even more maintainable and flexible, and also yields faster build times with the new --build flag.
We briefly went through how using defaultprops in a React component has improved. We'll be using this frequently as we start to learn how to build strongly-typed React components in subsequent chapters.
So, now that we are starting to get comfortable with TypeScript, in the next chapter, we'll get started with React. We'll start by learning how to create a React and TypeScript project, and then move on to how to create React and TypeScript components.
In order to cement what we have learned about TypeScript 3, have a go at the following questions:
function drawPoint(x: number, y: number, z: number) {
...
}
We also have the following point variable:
const point: [number, number, number] = [100, 200, 300];
How can we call the drawPoint function in a terse manner?
drawPoint(1, 2, 3);
Internally, in the implementation of drawPoint, we draw the point from a tuple type [number, number, number]. How can we define the method parameter(s) with the required tuple?
function getData(resource: string): any {
const data = ... // call the web API
if (resource === "person") {
data.fullName = `${data.firstName} ${data.surname}`;
}
return data;
}
How can we make getData more type-safe by leveraging the unknown type?
The following links are good resources for further information on TypeScript 3.0:
React is a JavaScript library that helps us build the frontend of an app. It allows us to structure our apps using powerful and reusable components. It helps us manage the data that the components use, and their state, in a structured fashion. It uses something called a virtual DOM to efficiently render our frontend.
TypeScript can work beautifully with React, giving us the ability to add static types to our React components. The types help our code editor to surface problems while we write our React components, and give us tools to safely refactor them.
In this chapter, we'll look at two different ways to create a React and TypeScript project. We'll create our first React component, which will be a confirmation dialog. Early topics we'll cover are JSX and strongly typed props. We'll look at handling the dialog's button click events.
We'll then look at declaring and interacting with strongly typed states, which will be used to hide and show the dialog. We'll discuss component life cycle methods, and touch on the ones that have been removed in React 17.
Finally, we'll look at function components, and when these are used.
In this chapter, we'll cover the following topics:
We use the following technologies in this chapter:
There are several ways to create a React and TypeScript project. We'll start by quickly creating a project using a popular tool called create-react-app.
We'll then create a project in a more manual way, helping us to understand all the different pieces in play.
create-react-app is a command-line tool that we can use to quickly create a React and TypeScript app with lots of useful pieces.
Open Visual Studio Code in an empty folder of your choice. Let's create an app using this tool:
npx create-react-app my-react-ts-app --typescript
The npx tool temporarily installs the create-react-app npm package and uses it to create our project.
We chose to call our project my-react-ts-app. We also specified --typescript, which is the bit that tells the tool to set the project up with TypeScript.
The tool will take a minute or so to create your project.
Note that the version of React we use needs to be at least version 16.7.0-alpha.0. We can check this in the package.json file. If the version of React in package.json is less that 16.7.0-alpha.0 then we can install this version using the following command:
npm install react@16.7.0-alpha.0
npm install react-dom@16.7.0-alpha.0
cd my-react-ts-app
npm install tslint tslint-react tslint-config-prettier --save-dev
{
"extends": ["tslint:recommended", "tslint-react", "tslint-
config-prettier"],
"rules": {
"ordered-imports": false,
"object-literal-sort-keys": false,
"no-debugger": false,
"no-console": false,
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
Here we are merging the generally recommended rules with specific ones for React and Prettier. We've enabled the use of debugger and console statements, which will come in handy from time to time as we develop our app.
We've also suppressed the rule about the ordering of import statements and object literal keys, to make life easier as we copy bits of code from this book.
npm start
After a few seconds, a browser window opens, with our app running:

Our React code is in the src folder.

So, let's fix that by adding public as the modifier:
class App extends Component {
public render() {
return ( ... );
}
}
<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
Learn React and TypeScript
</a>

create-react-app has configured a lot of great stuff for us in our project. This is great if we just want to quickly start learning React, and skip over how the React and TypeScript code is packaged up to be served from a web server.
In the next section, we'll do manually do some of what create-react-app did for us automatically. This will start to give us an understanding of what needs to happen when React and TypeScript apps are packaged up.
In this section, we'll create a React and TypeScript project manually, step by step. We'll start by creating our folder structure.
We need a folder structure that gives us decent separation between the project's configuration files, source code, and files to distribute to our web server.
All our configuration files will go in our project route:
The package.json file defines our project name, description, build commands, dependent npm modules, and much more.
Open a terminal window, and run the following command:
npm init
This will prompt you for various bits of information about the project, and then create a package.json file containing that information.
We installed TypeScript globally in Chapter 1, TypeScript Basics. In this section, we are going to install it locally within our project. Having TypeScript locally simplifies the build process a little bit.
We can install TypeScript just within our project by running the following command in the terminal:
npm install typescript --save-dev
As outlined in Chapter 1, TypeScript Basics, tsconfig.json specifies how our TypeScript code is compiled and transpiled.
Let's create a new file called tsconfig.json in the root of our project, and enter the following:
{
"compilerOptions": {
"target": "es5",
"module": "es6",
"moduleResolution": "node",
"lib": ["es6", "dom"],
"sourceMap": true,
"jsx": "react",
"strict": true,
"noImplicitReturns": true,
"rootDir": "src",
"outDir": "dist",
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
in Chapter 1, TypeScript Linting, introduced us to TSLint. Add it to your project as follows:
npm install tslint --save-dev
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"linterOptions": {
"exclude": ["node_modules/**/*.ts"]
}
}
Let's add the React library to our project, by running the following command in the terminal:
npm install react react-dom
We also want the TypeScript types for React. So, add these to our project as a development dependency, as follows:
npm install @types/react @types/react-dom --save-dev
We need an HTML page that is going to host our React app. Create a file called index.html in our dist folder, and enter the following:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
The HTML from our React app will be injected into the div with id ="root". All the app's JavaScript code will eventually be bundled together into a file called bundle.js in the dist folder.
Of course, neither of these exist at the moment—we'll do this in a later section.
Let's create a very simple React component. Create a file called index.tsx in your src folder, and enter the following:
import * as React from "react";
const App: React.SFC = () => {
return <h1>My React App!</h1>;
};
Our component simply returns My React App! in an h1 tag.
The next step is to inject our React component into index.html. We can do that by using the ReactDOM.render function. ReactDOM.render takes in our component as the first parameter, and the HTML element to inject it into as the next element.
Let's add the highlighted lines into index.tsx:
import * as React from "react";
import * as ReactDOM from "react-dom";
const App: React.SFC = () => {
return <h1>My React App!</h1>;
};
ReactDOM.render(<App />, document.getElementById("root") as HTMLElement);
Now that we have a small app in place, we need to package it up. We'll cover that in the next section.
Webpack is a popular tool that we can use to bundle all our JavaScript code into the bundle.js file that our index.html is expecting.
npm install webpack webpack-cli --save-dev
npm install webpack webpack-dev-server --save-dev
npm install ts-loader --save-dev
const path = require("path");
module.exports = {
entry: "./src/index.tsx",
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
}
]
},
resolve: {
extensions: [".tsx", ".ts", ".js"]
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "dist"),
compress: true,
port: 9000
}
};
There's a fair bit going on here, so let's break it down:
We should now have the following folders, with the following files within them:
├─ dist/
├─ bundle.js
├─ index.html
├─ node_modules/
├─ src/
├─ index.tsx
├─ package.json
├─ tsconfig.json
├─ tslint.json
├─ webpack.config.js
We are nearly ready to run our app now—there's just one more thing to do, as we'll discuss in the next section.
We are going to leverage npm scripts to start our app in development mode, and also to build a production version of our app:
{
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"start": "webpack-dev-server --env development",
"build": "webpack --env production"
},
..
}
npm run build
Now, webpack will kick in and do its stuff. If we look in the dist folder, eventually a file called bundle.js will appear. This file contains all the JavaScript minified code, including code from the React library and our simple React component.
npm start
The webpack development server will start.

const App: React.SFC = () => {
return <h1>My React and TypeScript App!</h1>;
};

We'll leave our manually-configured project there. It doesn't do as much as the create-react-app project, but we have started to gain an understanding of how React and TypeScript projects are packaged up.
So far we have created some very simple components. In this section, we are going to build a component that is a little more complex, and start to get more familiar with some of the different parts of a component.
Together, we'll start to build a component called Confirm that will allow a user to either continue with an operation or stop.
Our component will look like the following screenshot when we've finished:

We're going to use create-react-app to spin up a project quickly, as follows:
npx create-react-app my-components --typescript
This time we chose to call our project my-components.
cd my-components
npm install tslint tslint-react tslint-config-prettier --save-dev
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"rules": {
"ordered-imports": false,
"object-literal-sort-keys": false,
"no-debugger": false,
"no-console": false,
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
class App extends Component {
public render() {
return ( ... );
}
}
npm start
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}
.App-header {
background-color: #282c34;
height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 16px;
color: white;
}
The app header should now be a little shorter.
We'll keep our app running while we develop our component in the following sections.
Let's look at App.tsx, which has been created for us. This is an example of a class component. We're going to create our own class component now. Follow these steps:
import * as React from "react";
class Confirm extends React.Component {
}
export default Confirm;
We learned all about classes in Chapter 1, TypeScript Basics. Here we are creating a class that extends the standard Component class from React. Note that we've imported React at the top of our file, and also that we are exporting our class component using a default export at the bottom of our file.
class Confirm extends React.Component {
public render() {
return (
);
}
}
The render method determines what the component needs to display. We define what needs to be displayed using JSX. In simple terms, JSX is a mix of HTML and JavaScript. We'll explore it in more detail in the next section.
public render() {
return (
<div className="confirm-wrapper confirm-visible">
<div className="confirm-container">
<div className="confirm-title-container">
<span>This is where our title should go</span>
</div>
<div className="confirm-content-container">
<p>This is where our content should go</p>
</div>
<div className="confirm-buttons-container">
<button className="confirm-cancel">Cancel</button>
<button className="confirm-ok">Okay</button>
</div>
</div>
</div>
);
}
At the moment, our render method looks a lot more like HTML than JavaScript, apart from that funny className attribute—shouldn't that be class?
We'll cover this and JSX in a little more detail in the next section, but before that, let's consume our Confirm component in the App component.
import Confirm from "./Confirm";
<div className="App">
<header className="App-header">
...
</header>
<Confirm />
</div>
If we look at the browser page where our app is running, it should now look like the following:

.confirm-wrapper {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: gray;
opacity: 0;
visibility: hidden;
transform: scale(1.1);
transition: visibility 0s linear 0.25s, opacity 0.25s 0s, transform 0.25s;
z-index: 1;
}
.confirm-visible {
opacity: 1;
visibility: visible;
transform: scale(1);
transition: visibility 0s linear 0s, opacity 0.25s 0s, transform 0.25s;
}
.confirm-container {
background-color: #fff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 0.2em;
min-width: 300px;
}
.confirm-title-container {
font-size: 1.3em;
padding: 10px;
border-top-left-radius: 0.2em;
border-top-right-radius: 0.2em;
}
.confirm-content-container {
padding: 0px 10px 15px 10px;
}
.confirm-buttons-container {
padding: 5px 15px 10px 15px;
text-align: right;
}
.confirm-buttons-container button {
margin-left: 10px;
min-width: 80px;
line-height: 20px;
border-style: solid;
border-radius: 0.2em;
padding: 3px 6px;
cursor: pointer;
}
.confirm-cancel {
background-color: #fff;
border-color: #848e97;
}
.confirm-cancel:hover {
border-color: #6c757d;
}
.confirm-ok {
background-color: #848e97;
border-color: #848e97;
color: #fff;
}
.confirm-ok:hover {
background-color: #6c757d;
border-color: #6c757d;
}
import "./Confirm.css";
Our component in the browser page should now look like the following:

So, a React class component has a special method called render, where we define what our component displays in JSX.
In the next section, we'll take a little break from our confirmation component while we learn a little more about JSX.
As mentioned in the previous section, JSX looks a bit like HTML. We can have JSX in our JavaScript (or TypeScript) code, as we did in the last section in our render function. JSX isn't valid JavaScript though—we need a preprocessor step to convert it into JavaScript.
We're going to use an online Babel REPL to play with JSX:
<span>This is where our title should go</span>
The following appears in the right-hand pane, which is what our JSX has compiled down to:
React.createElement(
"span",
null,
"This is where our title should go"
);
We can see that it compiles down to a call to React.createElement, which has three parameters:
<div className="confirm-title-container">
<span>This is where our title should go</span>
</div>
This now compiles down to two calls to React.createElement, with span being passed in as a child to div:
React.createElement(
"div",
{ className: "confirm-title-container" },
React.createElement(
"span",
null,
"This is where our title should go"
)
);
const props = {
title: "React and TypeScript"
};
<div className="confirm-title-container">
<span>{props.title}</span>
</div>
This is more interesting now. It compiles down to this:
var props = {
title: "React and TypeScript"
};
React.createElement(
"div",
{ className: "confirm-title-container" },
React.createElement(
"span",
null,
props.title
)
);
The key point is that we can inject JavaScript into HTML by using curly braces.
const props = {};
<div className="confirm-title-container">
<span>{props.title ? props.title : "React and TypeScript"}</span>
</div>
We see that the nested call to React.createElement uses our ternary as the child of span:
React.createElement(
"span",
null,
props.title ? props.title : "React and TypeScript"
)
So, why do we use the className attribute rather than class? Well, we now understand that JSX compiles down to JavaScript, and as class is a keyword in JavaScript, having a class attribute in JSX would clash. So, React uses className instead for CSS class references.
Now that we understand a little more about JSX, let's come back to our Confirm component.
At the moment, the title and content text for our Confirm component is hardcoded. Let's change these to reference properties (props) that the component takes in.
interface IProps {
title: string;
content: string;
}
class Confirm extends React.Component<IProps>
...
<div className="confirm-title-container">
<span>{this.props.title}</span>
</div>
<div className="confirm-content-container">
<p>{this.props.content}</p>
</div>
...
Note that we now have a TypeScript compilation error when we reference our Confirm component in App.tsx. This is because our component now expects title and content attributes, as follows:

<Confirm
title="React and TypeScript"
content="Are you sure you want to learn React and TypeScript?"
/>
The compilation error now goes away, and if we look at the browser, our component is rendered exactly as it was before we implemented props.
Interface props can be optional, as we discovered in Chapter 1, TypeScript Basics. So, we can also use this mechanism to add optional props to a React component.
Let's add some optional props to allow the button captions to be configurable on our confirmation component:
interface IProps {
title: string;
content: string;
cancelCaption?: string;
okCaption?: string;
}
<div className="confirm-buttons-container">
<button className="confirm-cancel">
{this.props.cancelCaption}
</button>
<button className="confirm-ok">
{this.props.okCaption}
</button>
</div>
If we look at the browser now, we have no button captions in our running app:

This is because we haven't supplied these values when we reference Confirm in App.tsx.
In the next section, we'll resolve this issue by adding some default values for cancelCaption and okCaption.
Default values can be added to component props when the component is initialized. These can be implemented using a static object literal called defaultProps.
Let's make use of this feature in our Confirm component, as follows:
class Confirm extends React.Component<IProps> {
public static defaultProps = {
cancelCaption: "Cancel",
okCaption: "Okay"
};
public render() { ... }
}
If we look at our running app again, we have button captions once more.
<Confirm
title="React and TypeScript"
content="Are you sure you want to learn React and TypeScript?"
cancelCaption="No way"
okCaption="Yes please!"
/>
Our running app should now look like the following:

Optional props with default values can make components easier to consume, because the most common configurations can be automatically set up without needing to specify anything.
So, our Confirm component is nice and flexible now, but the buttons don't do anything yet. We'll tackle this in the following sections.
Events exist in many programming languages. They allow us to specify logic to be executed, depending on how a user interacts with our app.
All the native JavaScript events are available for us to handle in JSX. JSX allows us to do this with props that call functions. The prop name is derived by prefixing the native event name with on, using camel case. So the prop name for the click event is onClick in JSX.
We need to be able to control what the buttons do in our Confirm component. Follow these steps:
<button className="confirm-ok" onClick={this.handleOkClick}>...</button>
So, we're telling the button to call a handleOkClick method within our Confirm class when it is clicked.
private handleOkClick() {
console.log("Ok clicked");
}
Switch to the browser with our app running, and click the Yes please! button. If we look in the console, we should see Ok clicked displayed:

As we can see, it is pretty straightforward to handle events, using mechanisms we are already familiar with. There is actually a problem with our handleOkClick method, though. We'll drill into this and resolve the issue in the next section.
Our event handler suffers from JavaScript's classic this problem. We aren't currently referencing this in our event handler, which is why the problem hasn't surfaced yet.
Let's expose the problem with the handleOkClick method, so that we can better understand what's happening:
private handleOkClick() {
console.log("Ok clicked", this.props);
}
Now, try clicking the Yes please! button again. The following error appears:

The problem is that this doesn't reference our class in the event handler—it is undefined instead.
One solution is to change the handleOkClick method to be an arrow function.
private handleOkClick = () => {
console.log("Ok clicked", this.props);
};
Now try clicking the Yes please! button again. We should see props successfully output to the console.
<button className="confirm-cancel" onClick={this.handleCancelClick}>...</button>
private handleCancelClick = () => {
console.log("Cancel clicked", this.props);
};
To summarize, in order to avoid the this problem, we can use arrow functions to implement event handlers.
Next, we want the consumer of the component to be able to execute some logic when the buttons are clicked. We'll cover how to do this in the next section.
In the previous section, we saw how props can be set for functions with our onClick event handlers. In this section, we'll implement our own function props, so that the consumer of our component can execute some logic when the Ok and Cancel buttons are pressed.
interface IProps {
title: string;
content: string;
cancelCaption?: string;
okCaption?: string;
onOkClick: () => void;
onCancelClick: () => void;
}
The props are required, so, we immediately get a compilation error in App.tsx when we reference the Confirm component. We'll fix this a little later.
private handleCancelClick = () => {
this.props.onCancelClick();
};
private handleOkClick = () => {
this.props.onOkClick();
};
private handleCancelConfirmClick = () => {
console.log("Cancel clicked");
};
private handleOkConfirmClick = () => {
console.log("Ok clicked");
};
<Confirm
...
onCancelClick={this.handleCancelConfirmClick}
onOkClick={this.handleOkConfirmClick}
/>
If we go back to our app, we'll see the compilation errors have been resolved. If we click the Ok and Cancel buttons, we get the message output to the console as expected:

At the moment, our buttons still don't do anything other than log a message to the console. We want the confirmation dialog to close when we click either button. We'll implement this in the next section.
State is an object that determines how the component behaves and renders. We need to introduce state into our app, in order to manage whether our confirmation dialog is open or closed.
This particular state is going to live and be managed within the App component, and be passed in as a prop to the Confirm component.
interface IProps {
open: boolean;
title: string;
content: string;
cancelCaption?: string;
okCaption?: string;
onOkClick: () => void;
onCancelClick: () => void;
}
So, on the outermost div, let's use a JavaScript ternary expression in the className attribute to only include confirm-visible when the open prop is true, as follows:
public render() {
return (
<div
className={
this.props.open
? "confirm-wrapper confirm-visible"
: "confirm-wrapper"
}
>
...
</div>
);
}
We now have a compilation error in App.tsx because we haven't specified the open attribute where we use Confirm.
<Confirm
open={false}
title="React and TypeScript"
content="Are you sure you want to learn React and TypeScript?"
cancelCaption="No way"
okCaption="Yes please!"
onCancelClick={this.handleCancelConfirmClick}
onOkClick={this.handleOkConfirmClick}
/>
If we look at the app in the browser now, the compilation error will have disappeared, and our confirmation dialog will be closed.
Let's create a state in App.tsx, and properly manage whether the confirmation dialog is open or closed:
interface IState {
confirmOpen: boolean;
}
class App extends React.Component<{}, IState>
Now that we have specified that our component has a state, we need to initialize it. We initialize component state in the class constructor.
constructor(props: {}) {
super(props);
this.state = {
confirmOpen: true,
};
}
We call super because our class extends React.Component.
The state is held in a private prop in a component class. In the constructor, we can set the state to our required object literal, which in our case has confirmOpen set to true.
<Confirm
open={this.state.confirmOpen}
...
/>
If we look at our running app, the confirmation dialog should be open again.
So, a private state prop gives us access to the component state, and we can initialize this in the class constructor.
When the confirmation dialog buttons are clicked, we want to close the dialog. So, we want to change the state of confirmOpen to be false when the buttons are clicked.
We already have arrow function handlers for the button click events, so perhaps we can change state in there:
private handleOkConfirmClick = () => {
this.state.confirmOpen = false;
};
We get a compilation error, as follows:

The error message is saying that the state is read-only! Why is this so, and how can we change the state?
We need to use a method called setState in the component class to change state. This helps ensure we manage state robustly and efficiently. setState takes a parameter, which is an object literal containing the state we want to change.
private handleOkConfirmClick = () => {
this.setState({ confirmOpen: false });
};
The compilation error disappears, and if we click Yes please! in the running app, the confirmation dialog will now close. We have successfully changed the state.
private handleCancelConfirmClick = () => {
this.setState({ confirmOpen: false });
};
After we close the confirmation dialog, we have no way to open it.
<button onClick={this.handleConfirmClick}>Confirm</button>
<Confirm ... />
private handleConfirmClick = () => {
this.setState({ confirmOpen: true });
};
We can now click the Confirm button to reopen the confirmation dialog when it has been closed.
interface IState {
confirmOpen: boolean;
confirmMessage: string;
}
constructor(props: {}) {
super(props);
this.state = {
confirmMessage: "Please hit the confirm button",
confirmOpen: true,
};
}
private handleOkConfirmClick = () => {
this.setState({
confirmMessage: "Cool, carry on reading!",
confirmOpen: false
});
};
private handleCancelConfirmClick = () => {
this.setState({
confirmMessage: "Take a break, I'm sure you will later ...",
confirmOpen: false
});
};
<p>{this.state.confirmMessage}</p>
<button onClick={this.handleConfirmClick}>Confirm</button>
<Confirm ... />
If we play with the running app now, we'll see the message in our app changing depending on whether we okay or cancel the confirmation dialog.
Although we can set the state prop directly in the constructor when we initialize it, we can't elsewhere in a class component. Instead, state should be changed by calling the setState method in the component class.
Life cycle methods in a class component allow us to run code at particular points in the process. The following is a high-level diagram of the component process, showing when the different methods are invoked:

componentDidMount is invoked when a component has been inserted into the DOM. Here are some common use cases for this method:
We're going to change the app we have been building to give users a time limit of 10 seconds to confirm whether or not they want to learn React and TypeScript. In order to do this, we'll need to make use of the componentDidMount method:
constructor(props: {}) {
super(props);
this.state = {
confirmMessage: "Please hit the confirm button",
confirmOpen: false
};
}
interface IState {
confirmOpen: boolean;
confirmMessage: string;
confirmVisible: boolean;
countDown: number;
}
constructor(props: {}) {
super(props);
this.state = {
confirmMessage: "Please hit the confirm button",
confirmOpen: false,
confirmVisible: true,
countDown: 10
};
}
private timer: number = 0;
public componentDidMount() {
this.timer = window.setInterval(() => this.handleTimerTick(), 1000);
}
private handleTimerTick() {
this.setState(
{
confirmMessage: `Please hit the confirm button ${
this.state.countDown
} secs to go`,
countDown: this.state.countDown - 1
}
);
}
We are reducing our counter as well, updating the message shown to the user in this method. We need to do some more work here, though: we need to stop the timer, hide the Confirm button, and tell the user they are too late!
private handleTimerTick() {
this.setState(
{
confirmMessage: `Please hit the confirm button ${
this.state.countDown
} secs to go`,
countDown: this.state.countDown - 1
}
);
if (this.state.countDown <= 0) {
clearInterval(this.timer);
this.setState({
confirmMessage: "Too late to confirm!",
confirmVisible: false
});
}
}
However, this is incorrect, because the state is updated asynchronously, and so this.state.countDown won't have necessarily updated the line after we update it in the setState call.
private handleTimerTick() {
this.setState(
{
confirmMessage: `Please hit the confirm button ${
this.state.countDown
} secs to go`,
countDown: this.state.countDown - 1
},
() => {
if (this.state.countDown <= 0) {
clearInterval(this.timer);
this.setState({
confirmMessage: "Too late to confirm!",
confirmVisible: false
});
}
}
);
}
private handleConfirmClick = () => {
this.setState({ confirmOpen: true });
clearInterval(this.timer);
};
private handleCancelConfirmClick = () => {
this.setState(...);
clearInterval(this.timer);
};
private handleOkConfirmClick = () => {
this.setState(...;
clearInterval(this.timer);
};
<p>{this.state.confirmMessage}</p>
{this.state.confirmVisible && (
<button onClick={this.handleConfirmClick}>Confirm</button>
)}
<Confirm ... />
Now, it's time to give this a try. We'll see the countdown when the app first runs:

If we don't confirm within ten seconds, it'll be too late:

componentWillUnmount is invoked just before the component is removed from the DOM. Here are some common use cases for this method:
We are going to use componentWillUnmount in our app to make sure our timer is stopped and removed. Let's add the following in the App class after the componentDidMount method:
public componentWillUnmount() {
clearInterval(this.timer);
}
getDerivedStateFromProps is invoked every time a component is rendered. It can be used to change state when certain props change. This is a static method in a component class that returns the changed state, or null if there are no changes to the state.
Let's have a look at this life cycle method in our app. Add the following at the top of the App class:
public static getDerivedStateFromProps(props: {}, state: IState) {
console.log("getDerivedStateFromProps", props, state);
return null;
}
If we look in the console when the app is running, we see that our method is called each time the countdown decrements:

getSnapshotBeforeUpdate is called just before the DOM is updated. The value that is returned from getSnapshotBeforeUpdate is passed on to componentDidUpdate.
componentDidUpdate is called as soon as the DOM is updated. Resizing the window during rendering is an example of when getSnapshotBeforeUpdate can be useful.
Let's have a look at these life cycle methods in our app:
private renderCount = 0;
public getSnapshotBeforeUpdate(prevProps: {}, prevState: IState) {
this.renderCount += 1;
console.log("getSnapshotBeforeUpdate", prevProps, prevState, {
renderCount: this.renderCount
});
return this.renderCount;
}
public componentDidUpdate(prevProps: {}, prevState: IState, snapshot: number) {
console.log("componentDidUpdate", prevProps, prevState,
snapshot, {
renderCount: this.renderCount
});
}
Look at the running app:

We see the methods being invoked in the order we expect, and componentDidUpdate successfully taking in the render count from getSnapshotBeforeUpdate.
shouldComponentUpdate is invoked just before rendering happens. It returns a Boolean value that determines whether rendering should happen. It can be used to optimize performance, preventing unnecessary render cycles.
public shouldComponentUpdate(nextProps: {}, nextState: IState) {
console.log("shouldComponentUpdate", nextProps, nextState);
return true;
}
If we look at the running app, we see that shouldComponentUpdate happens between getDerivedStateFromProps and getSnapshotBeforeUpdate, as we expect.
public shouldComponentUpdate(nextProps: {}, nextState: IState) {
console.log("shouldComponentUpdate", nextProps, nextState);
return false;
}
We see getSnapshotBeforeUpdate and componentDidUpdate aren't invoked, because no rendering occurs after the initial render:

public shouldComponentUpdate(nextProps: {}, nextState: IState) {
console.log("shouldComponentUpdate", nextProps, nextState);
return true;
}
shouldComponentUpdate can increase performance by stopping unnecessary rendering, but it should be used with care. It can introduce bugs that are hard to pin down. Also, the additional code we need to add to check whether a render should occur could in fact slow the app down.
There are a few life cycle methods that have been deprecated and renamed in React 17. We don't need to use these anymore—getDerivedStateFromProps and getSnapshotBeforeUpdate essentially replaced them. However, here's a brief description of these methods, in case you come across them in existing codebases:
As the name suggests, a function component is implemented using a JavaScript function. These components are sometimes referred to as functional stateless components, which can be a little confusing because they can contain states in more recent versions of React.
Let's refactor our Confirm component to be a function component, in order to learn how to implement these:
const Confirm: React.SFC<IProps> = (props) => {
...
}
We define a function component using an arrow function, passing the props type in as a generic parameter.
We use stateless functional component (SFC) React.SFC to represent these type of components.
Our component is now throwing several compilation errors. We'll resolve these in the next steps.
return (
<div
className={
this.props.open
? "confirm-wrapper confirm-visible"
: "confirm-wrapper"
}
>
...
</div>
);
const handleCancelClick = () => {
props.onCancelClick();
};
const handleOkClick = () => {
props.onOkClick();
};
return ( ... )
<div
className={
props.open
? "confirm-wrapper confirm-visible"
: "confirm-wrapper"
}
>
<div className="confirm-container">
<div className="confirm-title-container">
<span>{props.title}</span>
</div>
<div className="confirm-content-container">
<p>{props.content}</p>
</div>
<div className="confirm-buttons-container">
<button className="confirm-cancel" onClick=
{handleCancelClick}>
{props.cancelCaption}
</button>
<button className="confirm-ok" onClick={handleOkClick}>
{props.okCaption}
</button>
</div>
</div>
</div>
Confirm.defaultProps = {
cancelCaption: "Cancel",
okCaption: "Okay"
}
If we look at the running app, all the compilation errors should be resolved, and the app should be working as it was before.
The following code is a template for a function component. Our Confirm component should have a structure similar to this now:
import * as React from "react";
const ComponentName: React.SFC<IProps> = props => {
const handler = () => {
...
};
return (
<div>Our JSX</div>
);
};
ComponentName.defaultProps = {
...
};
export default ComponentName;
So, function components are an alternative way to create components. In the next section, we'll look at how to add state to a function component.
We've mentioned that function components can have state. In this section, we'll add state to our function Confirm component, to force users to click the Cancel button twice before closing it, as follows:
const Confirm: React.SFC<IProps> = props => {
const [cancelClickCount, setCancelClickCount] =
React.useState(0);
const handleOkClick = () => {
props.onOkClick();
};
...
}
This line of code looks a little strange, so let's break it down:
const handleCancelClick = () => {
const newCount = cancelClickCount + 1;
setCancelClickCount(newCount);
if (newCount >= 2) {
props.onCancelClick();
}
};
Now, functions to set the piece of state take in the new state as their parameter.
<button className="confirm-cancel" onClick={handleCancelClick}>
{cancelClickCount === 0 ? props.cancelCaption : "Really?"}
</button>
So, we access the state value in JSX through the variable we destructured when the state was defined.
If we give this a try in the running app, we should find the Cancel button text changes to Really? after the first click, and the confirmation dialog closes after the second click.
After we've got our heads around the code needed to define state, accessing and setting state is fairly simple and elegant.
Let's continue to the next section, and look into how we can hook into a function component's life cycle events.
We can invoke code to execute at certain points in a function component's life cycle. Let's explore this in our Confirm component, starting with when the component is first rendering, as follows:
const [cancelClickCount, setCancelClickCount] = React.useState(0);
React.useEffect(() => {
console.log("Confirm first rendering");
}, []);
React.useEffect(() => {
console.log("Confirm rendering");
});
If we look at the running app and the console, we'll see Confirm rendering appear each time Confirm is rendered.
React.useEffect(
() => {
console.log("open changed");
},
[props.open]
);
If we look at the running app and the console, we'll see open changed appear each time the Confirm component's open prop changes value.
React.useEffect(() => {
console.log("Confirm first rendering");
return () => {
console.log("Confirm unmounted");
};
}, []);
So, our arrow function can return a function that is executed when the component is unmounted.
{this.state.countDown > 0 && (
<Confirm
...
/>
)}
If we look at the running app and the console, we'll see Confirm unmounted appear when the countdown reaches 0.
So, we can execute logic in function components when they are first rendered, when their props change, and when they are unmounted.
In the next section, we'll look at a method we can use to optimize function component rendering cycles.
Our Confirm component is actually being rendered more than it needs to be. In this section, we are going to optimize this so that it only renders when its props change:
console.log("Confirm rendering");
If we look at the running app and the console, we'll see that a render occurs every time the App component counts down. The countdown is in the App component state, and a change to state means the component will be rendered again, along with any child components. This is why, without any optimization, our Confirm component renders on each countdown.
const ConfirmMemo = React.memo(Confirm);
export default ConfirmMemo;
So, we wrap our component with a function called memo from React. We then export this wrapper function. The memo function then only renders the component if its props change.
If we look at the running app and the console, we'll see that our component no longer renders on each countdown.
So, given how simple this is, shouldn't we just wrap all our function components with memo? No! There is a performance cost when memo determines whether a component has changed. If the component doesn't actually do any unnecessary rendering, using memo would result in the component being slower.
memo should be used with care, and only on components that are being rendered more than they need to be.
Given that the features of class components and function components are similar, which type should we be using? There is no straightforward answer, really. If our team is used to object-oriented code, perhaps class-based components will be easier to learn. If our team is used to more functional programming techniques, then function-based components may enable them to be more productive.
Both approaches are great ways to create React components—it's down to you to choose!
In this chapter we learned a couple of different ways we can create a React and TypeScript project. The more manual way taught us just how many moving parts there are. We'll use create-react-app regularly to quickly create our apps in this book.
We learned how all React class components have a render method that returns JSX, which tells React what to display. JSX is very flexible and powerful, because JavaScript expressions can be mixed in with HTML.
We learned how components can be configured using props, and how we can add TypeScript types to props, to help prevent mistakes when consuming components.
Next, we learnt how components manage what is rendered and how they behave using state. Like props, state in a React and TypeScript app is strongly typed. We initialize state in the constructor, and change it via a setState method.
We also learnt about event handlers, which allow us to react to how users interact with our components. JSX gives us handy onEventName attributes for handling events.
Next, we learnt about the various life cycle methods that can be implemented to execute logic at various points in the process. The most commonly used life cycle method is componentDidMount , which occurs when a component has just been added to the DOM.
Finally, we learned about function components, which are an alternative approach to implementing components. In recent versions of React, we are able to use state within them, access common life cycle hooks, and even optimize rendering cycles.
In Chapter 3, Getting Started with React and TypeScript, we will learn about how we can efficiently build React and TypeScript apps that have multiple pages.
Answer the following questions, based on what we have just learned:
const decrement = () => {
// TODO - reduce count by 1
};
How can this be implemented?
If our app has multiple pages, we need to manage the navigation between the different pages. React Router is a great library that helps us do just this!
In this chapter, we are going to build a web shop where we can purchase a few tools for React. Our simple shop will have multiple pages that we'll manage using React Router. The shop will look like the following screenshot when we are finished:

In this chapter, we'll learn the following topics:
We'll use the following technologies in this chapter:
React Router and its Types are in npm, so we can install them from there.
Before installing React Router, we need to create our React shop project. Let's get ready to do that by choosing an empty folder of our choice and opening Visual Studio Code. To do this, follow these steps:
npx create-react-app reactshop --typescript
Note that the version of React we use needs to be at least version 16.7.0-alpha.0. We can check this in the package.json file. If the version of React in package.json is less that 16.7.0-alpha.0, then we can install this version using the following command:
npm install react@16.7.0-alpha.0
npm install react-dom@16.7.0-alpha.0
cd reactshop
npm install tslint tslint-react tslint-config-prettier --save-dev
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"rules": {
"ordered-imports": false,
"object-literal-sort-keys": false,
"no-debugger": false,
"no-console": false,
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
npm install react-router-dom
npm install @types/react-router-dom --save-dev
Before going on to the next section, we're going to remove some of the files create-react-app created that we don't need:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import './index.css';
ReactDOM.render(
<div />,
document.getElementById('root') as HTMLElement
);
We declare the pages in our app using the BrowserRouter and Route components. BrowserRouter is the top-level component and this looks for Route components beneath it to determine all the different page paths.
We are going to declare some pages in our app using BrowserRouter and Route later in this section, but before that we need to create our first two pages. This first page is going to contain the list of our React tools that we are going to sell in our shop. We use the following steps to create our pages:
export interface IProduct {
id: number;
name: string;
description: string;
price: number;
}
export const products: IProduct[] = [
{
description:
"A collection of navigational components that compose
declaratively with your app",
id: 1,
name: "React Router",
price: 8
},
{
description: "A library that helps manage state across your app",
id: 2,
name: "React Redux",
price: 12
},
{
description: "A library that helps you interact with a GraphQL backend",
id: 3,
name: "React Apollo",
price: 12
}
];
import * as React from "react";
import { IProduct, products } from "./ProductsData";
interface IState {
products: IProduct[];
}
class ProductsPage extends React.Component<{}, IState> {
public constructor(props: {}) {
super(props);
this.state = {
products: []
};
}
}
export default ProductsPage;
public componentDidMount() {
this.setState({ products });
}
public render() {
return (
<div className="page-container">
<p>
Welcome to React Shop where you can get all your tools for ReactJS!
</p>
<ul className="product-list">
{this.state.products.map(product => (
<li key={product.id} className="product-list-item">
{product.name}
</li>
))}
</ul>
</div>
);
}
We have used the map function in the products array to iterate through the elements and produce a list item tag, li, for each product. We need to give each li a unique key attribute to help React manage any changes to the list items, which in our case is the id product.
.page-container {
text-align: center;
padding: 20px;
font-size: large;
}
.product-list {
list-style: none;
margin: 0;
padding: 0;
}
.product-list-item {
padding: 5px;
}
import * as React from "react";
const AdminPage: React.SFC = () => {
return (
<div className="page-container">
<h1>Admin Panel</h1>
<p>You should only be here if you have logged in</p>
</div>
);
};
export default AdminPage;
import * as React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import AdminPage from "./AdminPage";
import ProductsPage from "./ProductsPage";
We have renamed BrowserRouter to Router in the import statement to save a few keystrokes.
const Routes: React.SFC = () => {
return (
<Router>
<div>
<Route path="/products" component={ProductsPage} />
<Route path="/admin" component={AdminPage} />
</div>
</Router>
);
};
export default Routes;
import * as React from "react";
import * as ReactDOM from "react-dom";
import "./index.css";
import Routes from "./Routes";
ReactDOM.render(<Routes />, document.getElementById("root") as HTMLElement);
npm start
The app will probably start on the root page, which will be blank because that path doesn't point to anything.


Now that we have successfully created a couple of routes, we really need a navigation component to make our pages a little more discoverable. We will do just that in the next section.
React Router comes with some nice components for providing navigation. We are going to use these to implement navigation options in the app header.
We are going to use the Link component from React Router to create our navigation options by carrying out the following steps:
import * as React from "react";
import { Link } from "react-router-dom";
import logo from "./logo.svg";
const Header: React.SFC = () => {
return (
<header className="header">
<img src={logo} className="header-logo" alt="logo" />
<h1 className="header-title">React Shop</h1>
<nav>
<Link to="/products" className="header-
link">Products</Link>
<Link to="/admin" className="header-link">Admin</Link>
</nav>
</header>
);
};
export default Header;
.header {
text-align: center;
background-color: #222;
height: 160px;
padding: 20px;
color: white;
}
.header-logo {
animation: header-logo-spin infinite 20s linear;
height: 80px;
}
@keyframes header-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.header-title {
font-size: 1.5em;
}
.header-link {
color: #fff;
text-decoration: none;
padding: 5px;
}
import Header from "./Header";
<Router>
<div>
<Header />
<Route path="/products" component={ProductsPage} />
<Route path="/admin" component={AdminPage} />
</div>
</Router>


If we look at the Network tab in Developer tools while clicking the navigation options, we'll see no network request is being made to serve the pages. This shows that React Router is handling the navigation for us in our React app.
React Router providers another component for linking to pages, called NavLink. This is actually even more suitable for our requirements. The following steps explain how we can refactor our Header component to use NavLink:
import * as React from "react";
import { NavLink } from "react-router-dom";
import logo from "./logo.svg";
const Header: React.SFC = () => {
return (
<header className="header">
<img src={logo} className="header-logo" alt="logo" />
<h1 className="header-title">React Shop</h1>
<nav>
<NavLink to="/products" className="header-
link">Products</NavLink>
<NavLink to="/admin" className="header-
link">Admin</NavLink>
</nav>
</header>
);
};
export default Header;
At this point, our app looks and behaves exactly the same.
<NavLink to="/products" className="header-link" activeClassName="header-link-active">
Products
</NavLink>
<NavLink to="/admin" className="header-link" activeClassName="header-link-active">
Admin
</NavLink>
.header-link-active {
border-bottom: #ebebeb solid 2px;
}

So, NavLink is great for the main app navigation where we want to highlight the active link and Link is great for all the other links in our app.
A Route parameter is a variable part of the path that can used in the destination component to conditionally render something.
We need to add another page to our shop to show the description and price of each product, along with an option to add it to the basket. We want to be able to navigate to this page using the "/products/{id}" path, where id is the ID of the product. For example, the path to React Redux would be "products/2". So, the id part of the path is a route parameter. We can do all this by following these steps:
<Route path="/products" component={ProductsPage} />
<Route path="/products/:id" component={ProductPage} />
<Route path="/admin" component={AdminPage} />
import * as React from "react";
import { RouteComponentProps } from "react-router-dom";
import { IProduct, products } from "./ProductsData";
type Props = RouteComponentProps<{id: string}>;
Ideally, we'd have specified the id property as a number to match the type in the product data. However, RouteComponentProps only allows us to have Route parameters of type string or undefined.
interface IState {
product?: IProduct;
added: boolean;
}
class ProductPage extends React.Component<Props, IState> {
public constructor(props: Props) {
super(props);
this.state = {
added: false
};
}
}
export default ProductPage;
public componentDidMount() {
if (this.props.match.params.id) {
const id: number = parseInt(this.props.match.params.id, 10);
const product = products.filter(p => p.id === id)[0];
this.setState({ product });
}
}
Remember that the id route parameter is a string, which is why we cast it to a number using parseInt before comparing it with the product data in the filter array.
public render() {
const product = this.state.product;
return (
<div className="page-container">
{product ? (
<React.Fragment>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p className="product-price">
{new Intl.NumberFormat("en-US", {
currency: "USD",
style: "currency"
}).format(product.price)}
</p>
{!this.state.added && (
<button onClick={this.handleAddClick}>Add to
basket</button>
)}
</React.Fragment>
) : (
<p>Product not found!</p>
)}
</div>
);
}
There are a few interesting bits in this JSX:
private handleAddClick = () => {
this.setState({ added: true });
};
import ProductPage from "./ProductPage";

<Route exact={true} path="/products" component={ProductsPage} />

import { Link } from "react-router-dom";
public render() {
return (
<div className="page-container">
<p>
Welcome to React Shop where you can get all your tools
for ReactJS!
</p>
<ul className="product-list">
{this.state.products.map(product => (
<li key={product.id} className="product-list-item">
<Link to={`/products/${product.id}`}>{product.name}
</Link>
</li>
))}
</ul>
</div>
);
}
.product-list-item a {
text-decoration: none;
}
Now, if we go to the products list in our app and click on a list item, it takes us to the relevant product page.
What if a user enters a path that doesn't exist in our app? For example, if we try to navigate to "/tools" we get nothing appearing beneath our header. This makes sense, because React Router didn't find any matching routes, so nothing is rendered. However, if the user does navigate to an invalid path, we want to inform them that the path doesn't exist. The following steps make this happen:
import * as React from "react";
const NotFoundPage: React.SFC = () => {
return (
<div className="page-container">
<h1>Sorry, this page cannot be found</h1>
</div>
);
};
export default NotFoundPage;
import NotFoundPage from "./NotFoundPage";
<Router>
<div>
<Header />
<Route exact={true} path="/products" component={ProductsPage}
/>
<Route path="/products/:id" component={ProductPage} />
<Route path="/admin" component={AdminPage} />
<Route component={NotFoundPage} />
</div>
</Router>
However, this is going to render for every path:

How can we just render NotFoundPage when it hasn't found another route? The answer is to wrap the Routes in the Switch component in React Router.
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
<Switch>
<Route exact={true} path="/products" component={ProductsPage} />
<Route path="/products/:id" component={ProductPage} />
<Route path="/admin" component={AdminPage} />
<Route component={NotFoundPage} />
</Switch>
The Switch component renders only the first matching Route component. If we look at the running app we see that our problem is resolved. If we enter a path that doesn't exist, we get our nice not found message:

React Router has a component called Redirect that we can use to redirect to pages. We use this component in a couple of cases to improve our shop in the following sections.
If we visit the / route path, we'll notice that we get the Sorry, this page cannot be found message. Let's change this to redirect to "/products" when the path is /.
import { BrowserRouter as Router, Redirect, Route, Switch } from "react-router-dom";
<Switch>
<Redirect exact={true} from="/" to="/products" />
<Route exact={true} path="/products" component={ProductsPage}
/>
<Route path="/products/:id" component={ProductPage} />
<Route path="/admin" component={AdminPage} />
<Route component={NotFoundPage} />
</Switch>
We can use the Redirect component to protect pages from unauthorized users. In our shop, we can use this to ensure only logged in users can access our Admin page. We do this through the following steps:
<Route path="/login" component={LoginPage} />
import * as React from "react";
const LoginPage: React.SFC = () => {
return (
<div className="page-container">
<h1>Login</h1>
<p>You need to login ...</p>
</div>
);
};
export default LoginPage;
import LoginPage from "./LoginPage";

We are not going to fully implement our Login page; the page that we have implemented is enough to demonstrate a conditional redirect.
const Routes: React.SFC = () => {
const [loggedIn, setLoggedIn] = React.useState(false);
return (
<Router>
...
</Router>
);
};
So, we have used a useState hook to add a state variable called loggedIn and a function to set it called setLoggedIn.
<Route path="/admin">
{loggedIn ? <AdminPage /> : <Redirect to="/login"
/>}
</Route>
const [loggedIn, setLoggedIn] = React.useState(true);
A query parameter is part of the URL that allows additional parameters to be passed into a path. For example, "/products?search=redux" has a query parameter called search with a redux value.
Let's implement this example and allow the users of the shop to search for a product:
interface IState {
products: IProduct[];
search: string;
}
import { RouteComponentProps } from "react-router-dom";
class ProductsPage extends React.Component<RouteComponentProps, IState> {
public constructor(props: RouteComponentProps) {
super(props);
this.state = {
products: [],
search: ""
};
}
public static getDerivedStateFromProps(
props: RouteComponentProps,
state: IState
) {
const searchParams = new URLSearchParams(props.location.search);
const search = searchParams.get("search") || "";
return {
products: state.products,
search
};
}
npm install url-search-params-polyfill
import "url-search-params-polyfill";
<ul className="product-list">
{this.state.products.map(product => {
if (
!this.state.search ||
(this.state.search &&
product.name
.toLowerCase()
.indexOf(this.state.search.toLowerCase()) > -1)
) {
return (
<li key={product.id} className="product-list-item">
<Link to={`/products/${product.id}`}>{product.name}
</Link>
</li>
);
} else {
return null;
}
})}
</ul>

const [search, setSearch] = React.useState("");
import { NavLink, RouteComponentProps, withRouter} from "react-router-dom";
import "url-search-params-polyfill";
const Header: React.SFC<RouteComponentProps> = props => { ... }
const [search, setSearch] = React.useState("");
React.useEffect(() => {
const searchParams = new URLSearchParams(props.location.search);
setSearch(searchParams.get("search") || "");
}, []);
public render() {
return (
<header className="header">
<div className="search-container">
<input
type="search"
placeholder="search"
value={search}
onChange={handleSearchChange}
onKeyDown={handleSearchKeydown}
/>
</div>
<img src={logo} className="header-logo" alt="logo" />
<h1 className="header-title">React Shop</h1>
<nav>
...
</nav>
</header>
);
}
.search-container {
text-align: right;
margin-bottom: -25px;
}
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.currentTarget.value);
};
const handleSearchKeydown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
props.history.push(`/products?search=${search}`);
}
};
export default withRouter(Header);

Sometimes, we might want to ask the user to confirm that they want to navigate away from a page. This is useful if the user is in the middle of data entry on a page and presses a navigation link to go to a different page before they have saved the data. The Prompt component in React Router allows us to do this, as set out in the following steps:
import { Prompt, RouteComponentProps } from "react-router-dom";
<div className="page-container">
<Prompt when={!this.state.added} message={this.navAwayMessage}
/>
...
</div>
The when attribute allows us to specify an expression for when the dialog should appear. In our case, this is when the product hasn't been added to the basket.
The message attribute allows us to specify a function that returns the message to display in the dialog.
private navAwayMessage = () =>
"Are you sure you leave without buying this product?";

We are asked to confirm whether we want to navigate away.
A nested route is when a URL is more than one level deep and it renders multiple components. We are going to implement some nested routes in this section in our Admin page. Our completed Admin page will look like the following screenshot:

The URL in the preceding screenshot is 3 levels deep which renders the following:
import { NavLink, Route, RouteComponentProps } from "react-router-dom";
<div className="page-container">
<h1>Admin Panel</h1>
<ul className="admin-sections>
<li key="users">
<NavLink to={`/admin/users`} activeClassName="admin-link-
active">
Users
</NavLink>
</li>
<li key="products">
<NavLink to={`/admin/products`} activeClassName="admin-link-
active">
Products
</NavLink>
</li>
</ul>
</div>
We use the NavLink component to navigate to the nested route for the two options.
.admin-sections {
list-style: none;
margin: 0px 0px 20px 0px;
padding: 0;
}
.admin-sections li {
display: inline-block;
margin-right: 10px;
}
.admin-sections li a {
color: #222;
text-decoration: none;
}
.admin-link-active {
border-bottom: #6f6e6e solid 2px;
}
<div className="page-container">
<h1>Admin Panel</h1>
<ul className="admin-sections">
...
</ul>
<Route path="/admin/users" component={AdminUsers} />
<Route path="/admin/products" component={AdminProducts} />
</div>
const AdminProducts: React.SFC = () => {
return <div>Some options to administer products</div>;
};
So, this component just renders a bit of text on the screen.
interface IUser {
id: number;
name: string;
isAdmin: boolean;
}
const adminUsersData: IUser[] = [
{ id: 1, name: "Fred", isAdmin: true },
{ id: 2, name: "Bob", isAdmin: false },
{ id: 3, name: "Jane", isAdmin: true }
];
So, we have 3 users in our example.
const AdminUsers: React.SFC = () => {
return (
<div>
<ul className="admin-sections">
{adminUsersData.map(user => (
<li>
<NavLink
to={`/admin/users/${user.id}`}
activeClassName="admin-link-active"
>
{user.name}
</NavLink>
</li>
))}
</ul>
</div>
);
};
The component renders a link containing each user's name. The link is to a nested path which will eventually show details about the user.
<div>
<ul className="admin-sections">
...
</ul>
<Route path="/admin/users/:id" component={AdminUser} />
</div>
const AdminUser: React.SFC<RouteComponentProps<{ id: string }>> = props => {
return null;
};
We use RouteComponentProps to get the id from the URL path and make this available in the props.
const AdminUser: React.SFC<RouteComponentProps<{ id: string }>> = props => {
let user: IUser;
if (props.match.params.id) {
const id: number = parseInt(props.match.params.id, 10);
user = adminUsersData.filter(u => u.id === id)[0];
} else {
return null;
}
return null;
};
const AdminUser: React.SFC<RouteComponentProps<{ id: string }>> = props => {
let user: IUser;
if (props.match.params.id) {
const id: number = parseInt(props.match.params.id, 10);
user = adminUsersData.filter(u => u.id === id)[0];
} else {
return null;
}
return (
<div>
<div>
<b>Id: </b>
<span>{user.id.toString()}</span>
</div>
<div>
<b>Is Admin: </b>
<span>{user.isAdmin.toString()}</span>
</div>
</div>
);
};

So, in order to implement nested routes, we create the necessary links using NavLink or Link components and route those links to the component to render the content using a Route component. We already knew about these components before this section, so, we just needed to learn how to use these in the context of nested routes.
In this section, we are going to add a bit of animation when users navigate to different pages. We do this using the TransitionGroup and CSSTransition components from the react-transition-group npm package, as shown in the following steps:
npm install react-transition-group
npm install @types/react-transition-group --save-dev
TransitionGroup keeps track of all its children inside its local state and calculates when children are entering or exiting. CSSTransition takes whether children are leaving or exiting from TransitionGroup and applies CSS classes to the children based on that status. So, TransitionGroup and CSSTransition can wrap our routes and invoke CSS classes that we can create to animate pages in and out.
import { CSSTransition, TransitionGroup } from "react-transition-group";
import { Redirect, Route, RouteComponentProps, Switch} from "react-router-dom";
const Routes: React.SFC<RouteComponentProps> = props => {
...
}
<TransitionGroup>
<CSSTransition
key={props.location.key}
timeout={500}
classNames="animate"
>
<Switch>
...
</Switch>
</CSSTransition>
</TransitionGroup>
TransitionGroup requires children to have a unique key for it to determine what is exiting and entering. So, we have specified a key attribute on CSSTransition to be the location.key property from RouteComponentProps. We have specified that the transition is going to run for up to half a second via the timeout attribute. We have also specified the CSS classes that are going to be invoked with an animate prefix via the classNames attribute.
.animate-enter {
opacity: 0;
z-index: 1;
}
.animate-enter-active {
opacity: 1;
transition: opacity 450ms ease-in;
}
.animate-exit {
display: none;
}
CSSTransition is going to invoke these CSS classes when its key changes. The CSS classes initially hide the element being transitioned and gradually ease the element's opacity so that it shows.

Unfortunately, we can't use the withRouter higher order component because this would be outside the Router component. To resolve this, we can add a new component called RoutesWrap, which doesn't take in any props and wraps our existing Routes component. The Router will move up to RoutesWrap and will contain a Route component that always renders our Routes component.
const RoutesWrap: React.SFC = () => {
return (
<Router>
<Route component={Routes} />
</Router>
);
};
class Routes extends React.Component<RouteComponentProps, IState> {
...
}
export default RoutesWrap;
The compilation error goes away, which is great.
public render() {
return (
<div>
<Header />
<TransitionGroup>
...
</TransitionGroup>
</div>
);
}
If we go to the running app and navigate to the different pages, you'll see a nice fade animation as the page comes into view.
At the moment, all the JavaScript for our app is loaded when the app first loads. This includes the Admin page that users don't use that often. It would be great if the AdminPage component wasn't loaded when the app loads and instead loaded on demand. This is exactly what we are going to do in this section. This is called "lazy loading" components. The following steps allows us to load things on demand:
import { Suspense } from "react";
const AdminPage = React.lazy(() => import("./AdminPage"));
We use a React function called lazy which takes in a function that returns a dynamic import, which in turn is assigned to our AdminPage component variable.
"compilerOptions": {
"lib": ["es6", "dom"],
...
}
<Route path="/admin">
{loggedIn ? (
<Suspense fallback={<div className="page-container">Loading...</div>}>
<AdminPage />
</Suspense>
) : (
<Redirect to="/login" />
)}
</Route>
The Suspense component shows a div tag containing Loading... whilst AdminPage is being loaded.



In this example, the AdminPage component isn't that big so this approach doesn't really positively impact performance. However, loading larger components on demand can really help performance, particularly on slow connections.
React Router gives us a comprehensive set of components for managing the navigation between pages in our app. We learned that the top-level component is Router, which looks for Route components beneath it where we define what components should be rendered for certain paths.
The Link component allows us to link to different pages with an app. We learned that the NavLink component is like Link, but it includes the ability to style it depending on whether it is the active path or not. So, NavLink is perfect for the main navigation element in an app and Link is great for other links that appear on pages.
RouteComponentProps is a type that gives us access to route parameters and query parameters. We discovered that React Router doesn't parse query parameters for us, but can use the native JavaScript URLSearchParams interface to do this for us.
The Redirect component redirects to a path under certain conditions. We found that this is perfect for protecting pages that only privileged users can access.
The Prompt component allows us to ask the user to confirm they want to leave a page under a certain condition. We used this on the Product page to double-check whether users wanted to buy the product. Another common use case for this component is confirming navigation away from a data entry page when the inputted data hasn't been saved.
We learnt about how nested routes can provide users with deep links into very specific parts of our app. We simply define the relevant links using Link or NavLink and Route components to handle those links.
We improved our app experience with page transitions using the TransitionGroup and CSSTransition components from the react-transition-group npm package. We wrapped these components around our Route components that define the app paths and added CSS classes to do the animation we want when pages exit and enter into view.
We learnt that the React lazy function along with its Suspense component can be used on large components that are rarely used by users to load them on demand. This helps performance for the startup time of our app.
Let's test our knowledge on React Router with the following questions:
<Route path="/customers" component={CustomersPage} />
Will the CustomersPage component render when the page is "/customers"?
We've already learned about a fair amount of the type system in TypeScript. In this chapter, we'll continue on this journey, this time diving into some of the more advanced types and concepts that will help us later in the book to create reusable strongly type React components.
We'll learn about how we can combine existing types to create union type. We'll find out in Chapter 8, React Redux, that these types are fundamental to creating strongly typed React Redux code.
We briefly covered type guards in Chapter 2, What is New in TypeScript 3, when we learned about the unknown type. We look at these in more detail in this chapter.
Generics are a TypeScript feature that many libraries use to allow consumers to create strongly typed apps with their library. React itself uses it in class components to allow us to create strongly typed props and states in the component. We cover generics in detail in this chapter.
Overload signatures is a nice feature that allows us to have a single function taking different combinations of parameters. We'll learn how to use these in this chapter.
Lookup and mapped types allow us to dynamically create new types from existing types. We learn all about these at the end of this chapter.
In this chapter, we'll learn about the following topics:
We'll use the following technologies in this chapter:
As the name suggests, union types are types that we can combine together to form a new type. Unions are commonly used with string literal types, which we'll cover in the first section. Unions can be used in a pattern called discriminated union, which we can use when creating generic and reusable React components.
A variable of a string literal type can only be assigned the exact string value specified in the string literal type.
In the TypeScript playground, let's go through an example:
type Control = "Textbox";
let notes: Control;
notes = "Textbox";
As we would expect, the TypeScript compiler is happy with this.
notes = "DropDown";
We get the compilation error Type '"DropDown"' is not assignable to type '"Textbox"':

notes = null;
notes = undefined;
String literal types aren't that useful on their own. However, they become extremely useful when used in a union type, which we'll look at in the next section.
A string literal union type is where we combine multiple string literal types together.
Let's continue from the previous example and go through this.
type Control = "Textbox" | "DropDown"
We combine types in a union type using |.
let notes: Control;
notes = "Textbox";
notes = "DropDown";
type Control = "Textbox" | "DropDown" | "DatePicker" | "NumberSlider";
notes = "DatePicker";
notes = "NumberSlider";
If we think about it, this is really useful. We could have declared the notes variable as a string, but declaring it with the specific string literals it can contain makes it super type-safe.
The discriminated union pattern allows us to handle the logic for different union types.
Let's go through an example:
interface ITextbox {
control: "Textbox";
value: string;
multiline: boolean;
}
interface IDatePicker {
control: "DatePicker";
value: Date;
}
interface INumberSlider {
control: "NumberSlider";
value: number;
}
They all have a property called control, which will be the discriminant in the pattern.
type Field = ITextbox | IDatePicker | INumberSlider;
So, we can create union types from any types, and not just string literals. In this case, we have created a union type from three interfaces.
function intializeValue(field: Field) {
switch (field.control) {
case "Textbox":
field.value = "";
break;
case "DatePicker":
field.value = new Date();
break;
case "NumberSlider":
field.value = 0;
break;
default:
const shouldNotReach: never = field;
}
}
The value we need to set depends on the discriminant property, control. So, we have used a switch statement to branch on this property.
The default branch in the switch statement is where things get interesting. This branch should never be reached, so we have put a statement with the never type in that branch. We'll see the value of doing this after the next steps.
interface ICheckbox {
control: "Checkbox";
value: boolean;
}
type Field = ITextbox | IDatePicker | INumberSlider | ICheckbox;
We'll immediately see that our initializeValue function throws a compilation error on the never declaration:

This is very valuable because the never statement ensures we don't forget to add a branch of code for the new checkbox requirement.
function intializeValue(field: Field) {
switch (field.control) {
case "Textbox":
field.value = "";
break;
case "DatePicker":
field.value = new Date();
break;
case "NumberSlider":
field.value = 0;
break;
case "Checkbox":
field.value = false;
break;
default:
const shouldNotReach: never = field;
}
}
So, union types allow us to combine any types together to form another type. This allows us to create stricter types, particularly when working with strings. The discriminated union pattern allows us to have branches of logic for different types in the union, and the never type helps us catch all the changes that need to happen when we add a new type into the union type.
Type guards allow us to narrow down the specific type of an object within a conditional branch of code. They are useful when working with union types, when we need to implement a branch of code that deals with a specific type in the union.
We already worked with a type guard in the last section when we implemented the intializeValue function. The switch statement on the discriminant property control allowed us to set the value on each type in the union.
There are other ways we can implement type guards. The following sections go through different ways.
The typeof keyword is a JavaScript keyword that returns a string that represents the type. So, we can use this in a condition to narrow down the type.
Let's go through an example:
type StringOrStringArray = string | string[];
function first(stringOrArray: StringOrStringArray): string {
}
function first(stringOrArray: StringOrStringArray): string {
if (typeof stringOrArray === "string") {
return stringOrArray.substr(0, 1);
} else {
return stringOrArray[0];
}
}
If we hover over stringOrArray in the first branch, we see that the type has been successfully narrowed to string:

If we hover over stringOrArray in the second branch, we see that the type has been successfully narrowed to string[]:

console.log(first("The"));
console.log(first(["The", "cat"]));
If we run the program, T and The will be output to the console.
The typeof keyword can only be used with JavaScript types, though. To illustrate that point, let's create an enhanced version of our function:
function firstEnhanced(stringOrArray: StringOrStringArray): string {
if (typeof stringOrArray === "string") {
return stringOrArray.substr(0, 1);
} else if (typeof stringOrArray === "string[]") {
return stringOrArray[0];
} else {
const shouldNotReach: never = stringOrArray;
}
}
The TypeScript compiler isn't happy with the second branch:

The message gives us a clue as to what is going on. The JavaScript typeof keyword works with JavaScript types, which are string, number, boolean, symbol, undefined, object, and function; hence the union type in the error message combining these types. So, typeof in our second branch will actually return "object".
function firstEnhanced(stringOrArray: StringOrStringArray): string {
if (typeof stringOrArray === "string") {
return stringOrArray.substr(0, 1);
} else if (typeof stringOrArray === "object") {
return stringOrArray[0];
} else {
const shouldNotReach: never = stringOrArray;
}
}
The TypeScript compiler is now happy again.
So, typeof is great for branching on JavaScript types but not ideal for TypeScript specific types. Let's find out how we can bridge this gap in the following sections.
The instanceof keyword is another JavaScript keyword. It checks whether an object has a particular constructor function. It is typically used to determine whether an object is an instance of a class.
Let's go through an example:
class Person {
id: number;
firstName: string;
surname: string;
}
class Company {
id: number;
name: string;
}
type PersonOrCompany = Person | Company;
function logName(personOrCompany: PersonOrCompany) {
if (personOrCompany instanceof Person) {
console.log(`${personOrCompany.firstName} ${personOrCompany.surname}`);
} else {
console.log(personOrCompany.name);
}
}
When using instanceof, we have the variable we are checking before it and the constructor name (the class name) after it.
If we hover over personOrCompany in the first branch, we get the Person type:

If we hover over personOrCompany in the second branch, we get the Company type:

So, instanceof is great for narrowing down the type if we are dealing with classes. However, there are lots of TypeScript types we use that aren't JavaScript types or based on classes. So, what do we do in these situations? Let's find out in the following sections.
The in keyword is another JavaScript keyword that can be used to check whether a property is in an object.
Let's implement the example from the last section using the in keyword:
interface IPerson {
id: number;
firstName: string;
surname: string;
}
interface ICompany {
id: number;
name: string;
}
type PersonOrCompany = IPerson | ICompany;
function logName(personOrCompany: PersonOrCompany) {
if ("firstName" in personOrCompany) {
console.log(`${personOrCompany.firstName} ${personOrCompany.surname}`);
} else {
console.log(personOrCompany.name);
}
}
We put the property name in double quotes before the in keyword, followed by the object to check.
If we hover over personOrCompany in the first branch, we get the IPerson type. If we hover over personOrCompany in the second branch, we get the ICompany type.
So, the in keyword is pretty flexible. It can be used with any object to narrow down its type by checking if a property exists.
There is one final type guard we will go through in the next section.
In situations where we can't use the other type guards, we can create our own. We can do this by creating a function with the return type as type predicate. We actually used a user-defined type guard earlier in the book when we went through the unknown type.
Let's implement the example from the last two sections using our own type guard function:
interface IPerson {
id: number;
firstName: string;
surname: string;
}
interface ICompany {
id: number;
name: string;
}
type PersonOrCompany = IPerson | ICompany;
function isPerson(personOrCompany: PersonOrCompany): personOrCompany is IPerson {
return "firstName" in personOrCompany;
}
The type predicate personOrCompany is IPerson helps the TypeScript compiler narrow down the type. To confirm this, hovering over personOrCompany in the first branch should give the IPerson type. If we then hover over personOrCompany in the second branch, we should get the ICompany type.
Creating a user-defined type guard is a little more work than the other methods, but it gives us lots of flexibility to deal with cases if the other methods don't work.
Generics can be applied to a function or a whole class. It's a mechanism for allowing the consumers, own type to be used with the generic function or class. The following sections go through examples of both of these.
Let's go through an example of a generic function. We are going to create a wrapper function around the fetch JavaScript function for getting data from a web service:
function getData<T>(url: string): Promise<T> {
}
We place a T in angled brackets after the function name to denote that it is a generic function. We can actually use any letter, but T is commonly used. We then use T in the signature where the type is generic. In our example, the generic bit is the return type, so we are returning Promise<T>.
If we wanted to use an arrow function, this would be:
const getData = <T>(url: string): Promise<T> => {
};
function getData<T>(url: string): Promise<T> {
return fetch(url).then(response => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
});
}
interface IPerson {
id: number;
name: string;
}
getData<IPerson>("/people/1").then(person => console.log(person));
We pass the type we want to use in the function in angle brackets after the function name. In our case, it is IPerson.
If we hover over person in the then callback, we see that person is correctly typed to IPerson:

So, as the name suggests, a generic function is a function that works with a generic type. An alternative implementation for the previous example would be to use any as the return type, but that wouldn't be type-safe.
We can make a whole class generic. Let's dive into an example of a generic class that stores data in a list:
class List<T> {
}
We mark the class as generic by putting <T> after the class name.
private data: T[] = [];
We refer to the generic type using T. In our example, our data property is an array of whatever type the class has been declared with.
public getList(): T[] {
return this.data;
}
We reference the generic array as the return type with T[].
public add(item: T) {
this.data.push(item);
}
We reference the data item being passed in with the generic type T. The implementation simply uses the arrays push method to add the item to our private array.
public remove(item: T) {
this.data = this.data.filter((dataItem: T) => {
return !this.equals(item, dataItem);
});
}
private equals(obj1: T, obj2: T) {
return Object.keys(obj1).every(key => {
return obj1[key] === obj2[key];
});
}
We again reference the data item being passed in with the generic type T. The implementation uses the arrays filter method to filter the item out of our private array. The filter predicate uses a private method that checks whether two objects are equal.
interface IPerson {
id: number;
name: string;
}
const billy: IPerson = { id: 1, name: "Billy" };
const people = new List<IPerson>();
We put the type we want to use with the class after the class name in angled brackets.
people.add(billy);
people.remove(billy);
people.add({name: "Sally"});
We get a compilation error, as we would expect:

const items = people.getList();
If we hover over the items variable, we see that the type has been correctly inferred to IPerson[]:

So, a generic class allows us to use the class with different types but still maintain strong typing.
We actually used generic classes earlier in the book where we implemented React class components with props and state:
interface IProps { ... }
interface IState { ... }
class App extends React.Component<IProps, IState> {
...
}
Here, the React.Component class has two generic parameters for the props and state.
So, generics are a really important concept that we'll use heavily in this book to create strongly typed React components.
Overload signatures allow a function to be called with different signatures. This feature can be used nicely to streamline a set of functions that a library offers to consumers. Wouldn't it be nice for a library that contained condenseString public functions and condenseArray to be streamlined so that it just contained a single public condense function? We'll do just this in this section:
function condenseString(string: string): string {
return string.split(" ").join("");
}
function condenseArray(array: string[]): string[] {
return array.map(item => item.split(" ").join(""));
}
function condense(stringOrArray: string | string[]): string | string[] {
return typeof stringOrArray === "string"
? stringOrArray.split(" ").join("")
: stringOrArray.map(item => item.split(" ").join(""));
}
const condensedText = condense("the cat sat on the mat");
As we enter the function parameter, IntelliSense reminds us that we need to enter a string or an array of strings:

If we hover over the condensedText variable, we see that the inferred type is the union type:

function condense(string: string): string;
function condense(array: string[]): string[];
function condense(stringOrArray: string | string[]): string | string[] { ... }
We add function overload signatures before the main function signature. We've added an overload for when we work with a string, and a second overload for when we work with an array of strings.
const moreCondensedText = condense("The cat sat on the mat");
We get improved IntelliSense now as we type the parameter. We also get up and down arrows to scroll through the two different signatures:

If we hover over the moreCondensedText variable, we see that we get better type inference:

So, overload signatures improve the experience for developers consuming our functions. They can give improved IntelliSense and type inference.
The keyof is a keyword in TypeScript that creates a union type of all the properties in an object. The type that is created is called a lookup type. This allows us to create types dynamically, based on the properties of an existing type. It's a useful feature that we can use to create generic but strongly typed code against varying data.
Let's go through an example:
interface IPerson {
id: number;
name: string;
}
type PersonProps = keyof IPerson;
If we hover over the PersonProps type, we see that a union type containing "id" and "name" has been created:

interface IPerson {
id: number;
name: string;
age: number
}
If we hover over the PersonProps type again, we see that the type has been automatically extended to include "age":

So, the PersonProps type is a lookup type because it looks up the literals it needs to contain.
Let's create something useful now with a lookup type:
class Field {
name: string;
label: string;
defaultValue: any;
}
class Field<T, K extends keyof T> {
name: K;
label: string;
defaultValue: any;
}
We have created two generic parameters on the class. The first one is for the type of the object containing the field, and the second one is for the property name within the object.
const idField: Field<IPerson, "id"> = new Field();
const addressField: Field<IPerson, "address"> = new Field();
We get a compilation error, as we would expect:

Catching problems like this is the benefit of the lookup type, rather than using a string type.
idField.defaultValue = "2";
class Field<T, K extends keyof T> {
name: K;
label: string;
defaultValue: T[K];
}
We are looking up the type using T[K]. For idField, this will resolve to the type of the id property in IPerson, which is number.
The line of code that sets idField.defaultValue now throws a compilation error, as we would expect:

idField.defaultValue = 2;
The compilation error disappears.
So, lookup types can be useful when creating generic components for variable data types.
Let's move on to mapped types now. Again, these let us create new types from an existing type's properties. However, mapped types allow us to specifically define the properties in the new type by mapping them from the existing property.
Let's go through an example:
interface IPerson {
id: number;
name: string;
}
type ReadonlyPerson = { readonly [P in keyof IPerson]: IPerson[P] };
The important bit that creates the map is [P in keyof IPerson]. This iterates through all the properties in IPerson and assigns each one to P to create the type. So, the type that is generated in the previous example is the following:
type ReadonlyPerson = {
readonly id: number
readonly name: string
};
let billy: ReadonlyPerson = {
id: 1,
name: "Billy"
};
billy.name = "Sally";
As we expect, a compilation error is thrown where we try to set the readonly property to a new value:

So our mapped type worked! A more generic version of this mapped type is actually in TypeScript as a standard type, and is Readonly<T>.
let sally: Readonly<IPerson> = {
id: 1,
name: "sally"
};
Sally.name = "Billy";
A compilation error is thrown, as we would expect:

If we were in Visual Studio Code and used the Go to Definition option on the Readonly type, we would get the following:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
This is very similar to our ReadonlyPerson type, but IPerson has been substituted with generic type T.
Let's have a go at creating our own generic mapped type now:
type Stringify<T> = { [P in keyof T]: string };
let tim: Stringify<IPerson> = {
id: "1",
name: "Tim"
};
tim.id = 1
The expected compilation error is thrown:

So, mapped types are useful in situations when we need a new type that is based on an existing type. Along with Readonly<T>, there are quite a few standard mapped types in TypeScript, such as Partial<T>, which creates a mapped type making all the properties optional.
We've learned some of the more advanced types in TypeScript in this chapter, starting with union types. Union types are extremely useful, allowing us to create new types by unioning existing types together. We discovered that unioning together string literals allow us to create a type that is more specific and type-safe than a regular string.
We explored various ways of implementing type guards. Type guards are useful to help the compiler narrow down a union type in branches of logic. They are also useful when working with the unknown type to tell the compiler what the type is in branches of logic.
Generics, as the name suggests, allow us to create generic types. Having covered this topic in detail, the type-safety for props and state in React components makes a lot more sense now. We will continue to use generic classes and functions heavily in the rest of the book.
We learned that overload signatures allow us to have a function that has different parameters and return types. We can now use this feature to good effect to streamline public functions we expose in a library.
We learned about how we can dynamically create new types from existing type properties using both lookup and mapped types. We are now aware that there are lots of useful standard TypeScript mapped types such as Readonly<T> and Partial<T>.
Learning about all these features is great preparation for the next chapter, where we'll dive into some common patterns when working with React components.
Let's have a go at some questions on advanced types:
interface ICourseMark {
courseName: string;
grade: string;
}
We can use this interface as follows:
const geography: ICourseMark = {
courseName: "Geography",
grade: "B"
}
The grades can only be A, B, C, or D. How can we create a stronger typed version of the grade property in this interface?
function isNumberPopulated(field: number): boolean {
return field !== null && field !== undefined;
}
function isStringPopulated(field: string): boolean {
return field !== null && field !== undefined && field !== "";
}
How can we combine these into a single function called isPopulated with signature overloads?
type Stages = {
pending: 'Pending',
started: 'Started',
completed: 'Completed',
};
type Grade = 'gold' | 'silver' | 'bronze';
How can we programmatically create the following type:
type GradeMap = {
gold: string;
silver: string;
bronze: string
};
The TypeScript documentation has a great section on advanced types that is worth looking at:
https://www.typescriptlang.org/docs/handbook/advanced-types.html
In this chapter, we will continue with the React shop we were building previously. We'll build a reusable tab component as well as a reusable loading indicator component that will both be used on the product page in our shop. The chapter will start by splitting the product page into a container and a presentational component before working on the tab component, leveraging the compound component and render props patterns. We'll then move on to implement a loading indicator component using the higher-order component pattern.
In this chapter, we'll learn about the following topics:
We'll use the following technologies in this chapter:
Splitting pages up into container and presentational components makes the presentational component easier to reuse. The container component is responsible for how things work, fetching any data from a web API and managing state. The presentational component is responsible for how things look. Presentational components receive data via their properties and also have property event handlers so that their container can manage the user interactions.
We are going use this pattern in our React shop to split the product page into container and presentational components. The ProductPage component will be the container and we'll introduce a new component called Product that will be the presentational component:
npm start

import * as React from "react";
const Product: React.SFC<{}> = props => {
return <React.Fragment>TODO</React.Fragment>;
};
export default Product;
Our presentational component is a function component.
import * as React from "react";
import { IProduct } from "./ProductsData";
interface IProps {
product: IProduct;
inBasket: boolean;
onAddToBasket: () => void;
}
const Product: React.SFC<IProps> = props => {
return <React.Fragment>TODO</React.Fragment>;
};
export default Product;
const Product: React.SFC<IProps> = props => {
return (
<React.Fragment>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p className="product-price">
{new Intl.NumberFormat("en-US", {
currency: "USD",
style: "currency"
}).format(product.price)}
</p>
{!this.state.added && (
<button onClick={this.handleAddClick}>Add to basket</button>
)}
</React.Fragment>
);
};
We have a few reference issues now to resolve.
const product = props.product;
return (
...
)
{!props.inBasket && (
<button onClick={this.handleAddClick}>Add to basket</button>
)}
const product = props.product;
const handleAddClick = () => {
props.onAddToBasket();
};
return (
...
);
{!props.inBasket && (
<button onClick={handleAddClick}>Add to basket</button>
)}
That's our Product presentational component complete for the time being. So, let's reference our Product component in our ProductPage component.
import Product from "./Product";
return (
<div className="page-container">
<Prompt when={!this.state.added} message={this.navAwayMessage} />
{product ? (
<Product
product={product}
inBasket={this.state.added}
onAddToBasket={this.handleAddClick}
/>
) : (<p>Product not found!</p>)}
</div>
);
We pass the product, whether the product has been added to basket, and the handler for adding to the basket together as props to the Product component.
If we look at the shop again and go to the product page, it will look exactly the same.
So, we just implemented our first container and presentational components. Container components are great as the top-level component within a page, fetching data from a web API, and managing all the state within the page. Presentational components just focus on what needs to be rendered to the screen. A benefit of this pattern is that presentational components can be used elsewhere in the app more easily. For example, our Product component could fairly easily be used on other pages that we create in our shop. Another benefit of this pattern is that presentational components are generally easier to unit-test. In our example, our Product component is a pure function and so unit-testing this is simply a case of checking that the output is correct for different inputs because there are no side-effects. We'll cover unit testing in detail later in the book.
We'll continue to enhance our product page in the next section by adding reviews to it and adding tabs to separate the product description from the reviews.
Compound components are a set of components that work together. We are going to use this pattern to create a reusable tab component for use on the product page to separate reviews the product descriptions.
Before we create our Tabs compound component, let's add reviews to the product page:
export interface IReview {
comment: string;
reviewer: string;
}
export interface IProduct {
...
reviews: IReview[];
}
const products: IProduct[] = [
{
id: 1,
...
reviews: [
{
comment: "Excellent! This does everything I want",
reviewer: "Billy"
},
{ comment: "The best router I've ever worked with", reviewer:
"Sally" }
]
},
{
id: 2,
..
reviews: [
{
comment: "I've found this really useful in a large app I'm
working on",
reviewer: "Billy"
},
{
comment: "A bit confusing at first but simple when you get
used to it",
reviewer: "Sally"
}
]
},
{
id: 3,
..
reviews: [
{
comment: "I'll never work with a REST API again!",
reviewer: "Billy"
},
{
comment: "It makes working with GraphQL backends a breeze",
reviewer: "Sally"
}
]
}
];
So, we add a reviews property to each product that is an array of reviews. Each review is an object containing comment and reviewer properties as defined by the IReview interface.
<p>{product.description}</p>
<div>
<ul className="product-reviews">
{product.reviews.map(review => (
<li key={review.reviewer} className="product-reviews-item">
<i>"{review.comment}"</i> - {review.reviewer}
</li>
))}
</ul>
</div>
<p className="product-price">
...
</p>
So, we are using the map function on the reviews array to display comment and reviewer in a list.
.product-reviews {
list-style: none;
padding: 0px;
}
.product-reviews .product-reviews-item {
display: block;
padding: 8px 0px;
}
If we look at the running app and go to a product, we'll now see the reviews:

Now that we have added the reviews, we can work on our Tabs component in the next section.
Our job now is to separate the description from the reviews using a tab component that we are going to build. We are going to create a simple tab component first and refactor this into the compound component pattern in the next section.
It's time to start on our tab component:
import * as React from "react";
interface IProps {}
interface IState {}
class Tabs extends React.Component<IProps, IState> {
public constructor(props: IProps) {
super(props);
this.state = {};
}
public render() {
return;
}
}
export default Tabs;
We have chosen to create a class-based component because our component will have to track state for whichever tab heading is active.
interface IState {
activeHeading: string;
}
interface IProps {
headings: string[];
}
So, our component can take in an array of heading names in a headings prop.
public constructor(props: IProps) {
super(props);
this.state = {
activeHeading:
this.props.headings && this.props.headings.length > 0
? this.props.headings[0]
: ""
};
}
So, the active heading will initially be set to the first element in the headings array. The ternary ensures our component doesn't produce an error if no tabs have been passed to it by the consumer.
public render() {
return (
<ul className="tabs">
{this.props.headings.map(heading => (
<li className={heading === this.state.activeHeading ?
"active" : ""}
>
{heading}
</li>
))}
</ul>
);
}
We have referenced some CSS classes including active, which is set using a ternary based on whether it is the active tab heading being rendered or not.
.tabs {
list-style: none;
padding: 0;
}
.tabs li {
display: inline-block;
padding: 5px;
margin: 0px 5px 0px 5px;
cursor: pointer;
}
.tabs li:focus {
outline: none;
}
.tabs li.active {
border-bottom: #222 solid 2px;
}
Before we can see what our tab component looks like, we need to consume it.
import Tabs from "./Tabs";
<h1>{product.name}</h1>
<Tabs headings={["Description", "Reviews"]} />
<p>{product.description}</p>
We pass the Tabs component the two tab headings we want to display, which are Description and Reviews.
Let's see what this looks like:

That's a good start. The first tab is underlined from the active CSS style just as we wanted. If we click on the Reviews tab nothing happens, though.
<li
onClick={this.handleTabClick}
className={heading === this.state.activeHeading ? "active" : ""}
>
{heading}
</li>
private handleTabClick = (e: React.MouseEvent<HTMLLIElement>) => {
const li = e.target as HTMLLIElement;
const heading: string = li.textContent ? li.textContent : "";
this.setState({ activeHeading: heading });
};
We first extract the heading from the textContent of li. We then set the activeHeading state to this. This will cause React to re-render the component with the clicked tab shown as being active.
Notice that we help the TypeScript compiler declare the li variable as HTMLLIElement using the as keyword. Without doing this, the compiler wouldn't be happy with us accessing the textContent property within it.
If we go to the running app again, we can now click on our tabs and see the active state changing.
At the moment, our tabs component just renders some tabs that can be clicked on. It doesn't tie into any content yet. We'll actually not tie in our headings to content until the next section on the render props pattern. However, now it's time to explore the compound component pattern and enhance our tab headings a little more in the next section.
Our tab headings can only be strings at the moment. What if we want to allow the consumer of the component to define richer content in the headings? For example, a consumer might want to put an icon in front of a tab heading or make a heading bold. So, the consuming JSX could look something like this:
<Tabs>
<Tabs.Tab name="Description" initialActive={true}>
<b>Description</b>
</Tabs.Tab>
<Tabs.Tab name="Reviews">
Reviews
</Tabs.Tab>
</Tabs>
In the previous example, Tabs and Tabs.Tab are compound components:
So, let's refactor our basic tabs component into a compound component that can be used similarly to the previous example:
interface IState {
activeName: string;
}
class Tabs extends React.Component<{}, IState> {
public render() {
...
}
...
}
interface ITabProps {
name: string;
initialActive?: boolean;
}
class Tabs extends React.Component<IProps, IState> {
public static Tab: React.SFC<ITabProps> = props => <li>TODO - render the nodes child nodes</li>;
public render() {...}
...
}
This is the start of the component that will render each tab. The Tab component is defined as a static property on the Tabs component. This means Tab lives on the actual Tabs class and not in its instances. So, we must remember we don't have access to any Tabs instance members (for instance, this). However, we can reference Tab in JSX using Tabs.Tab now, which was one of our requirements.
At the moment, Tab is just rendering li with a note reminding us that we need to somehow render the child nodes of the component. Remember that we want the consuming markup for our Tabs component to be as follows:
<Tabs.Tab name="Description" initialActive={true}>
<b>Description</b>
/Tabs.Tab>
public static Tab: React.SFC<ITabProps> = props => <li>{props.children}</li>;
Our Tab component is not finished, but we'll leave it like this for the time being. We now need to move on to the Tabs component.
public render() {
return (
<ul className="tabs">{this.props.children}</ul>
);
}
We again use the magical children property to render the child nodes within Tabs.
We are progressing well with our compound Tabs and Tab components but our project no longer compiles because we have the tab click handler, handleTabClick, that is not referenced anymore. We need to somehow reference it from the Tab component when a tab heading is clicked, but remember Tab doesn't have access to members of Tabs. So, how can we do this? We'll find the answer to this problem in the next section.
React context allows state to be shared between components. It works really well with compound components. We are going to use it in our Tabs and Tab components to share state between them:
interface ITabsContext {
activeName?: string;
handleTabClick?: (name: string) => void;
}
So, our context will contain the active tab name as well as a reference to a tab click handler. These are the two bits of state that need to be shared between the components.
const TabsContext = React.createContext<ITabsContext>({});
We've used the createContext function in React to create our context, which is a generic function that creates a context of a generic type, which in our case in ITabsContext.
We are required to pass the default context value as the parameter value to createContext but that doesn't make sense in our case, so we just pass an empty {} object to keep the TypeScript compiler happy. This is why both the properties in ITabsContext are optional.
public render() {
return (
<TabsContext.Provider
value={{
activeName: this.state ? this.state.activeName : "",
handleTabClick: this.handleTabClick
}}
>
<ul className="tabs">{this.props.children}</ul>
</TabsContext.Provider>
);
}
There are a few things going on here, so let's break this down:
private handleTabClick = (name: string) => {
this.setState({ activeName: name });
};
public static Tab: React.SFC<ITabProps> = props => (
<TabsContext.Consumer>
{(context: ITabsContext) => {
const activeName = context.activeName
? context.activeName
: props.initialActive
? props.name
: "";
const handleTabClick = (e: React.MouseEvent<HTMLLIElement>) =>
{
if (context.handleTabClick) {
context.handleTabClick(props.name);
}
};
return (
<li
onClick={handleTabClick}
className={props.name === activeName ? "active" : ""}
>
{props.children}
</li>
);
}}
</TabsContext.Consumer>
);
This again looks a little daunting, so let's break it down:
So, that's it for our tabs compound component. The syntax for React context may seem a little strange at first, but when you get used to it, it is really simple and elegant.
Before we can give this a try, we need to consume our compound component in our Product component. Let's replace our previous consumption of the Tabs component with the following highlighted JSX:
<React.Fragment>
<h1>{product.name}</h1>
<Tabs>
<Tabs.Tab name="Description" initialActive={true}>
<b>Description</b>
</Tabs.Tab>
<Tabs.Tab name="Reviews">Reviews</Tabs.Tab>
</Tabs>
<p>{product.description}</p>
...
</React.Fragment>
This is exactly the JSX we wanted to achieve when we started to build the compound tabs component. If we go to the running app and browse to the product page, our tabs component works perfectly, with the description tab in bold:

So, compound components are great for components that rely on each other. The <Tabs.Tab /> syntax really calls out the fact that Tab needs to be used with Tabs.
React context works really well with compound components allowing the components, in the compound to easily share state. The state can even include functions such as event handlers.
Allowing the consumer to specify the content to be rendered in sections of a component gives the consumer a great deal of flexibility. Specifying this custom content as a child of a component is intuitive and feels natural. We'll continue with this approach in the following section where we'll complete our tabs component.
We used a form of the render props pattern in the previous section where we leveraged the children prop. We used this to allow a consumer of our Tab component to render custom content for the tab heading. This is great, but what if we want to allow the consumer to render custom content in different sections of the component? In our Tabs component, we haven't allowed the consumer to render the content of the tab yet. We definitely want the consumer to be able to specify custom content for this, but how do we do this now that we've already used the children prop for the heading?
The answer is simple but not obvious at first. The answer is that, because props can be anything, they can be a function that renders content – just like the special children prop. These types of prop are called render props. We can have as many render props as we like, giving us the flexibility to allow multiple sections of a component to be rendered by the consumer.
We actually used a render prop in the last section when we used React context. The way we consumed the context was via a render prop.
Next, we'll complete our Tabs component by leveraging the render props pattern.
We are going to complete our Tabs component now by using the render props pattern. Before we implement our first render prop, let's think about how we want the consumer to consume our Tabs component when it has been completed. The following JSX is how we would ideally consume the Tabs component from the Product component:
<Tabs>
<Tabs.Tab
name="Description"
initialActive={true}
heading={() => <b>Description</b>}
>
<p>{product.description}</p>
</Tabs.Tab>
<Tabs.Tab
name="Reviews"
heading={() => "Reviews"}
>
<ul className="product-reviews">
{product.reviews.map(review => (
<li key={review.reviewer}>
<i>"{review.comment}"</i> - {review.reviewer}
</li>
))}
</ul>
</Tabs.Tab>
</Tabs>
Let's go through the steps of the key parts in this:
So, let's change the implementation of the tab headings to use a render prop:
interface ITabProps {
name: string;
initialActive?: boolean;
heading: () => string | JSX.Element;
}
This property is a function with no parameters that returns a string or some JSX. This is the definition of our render prop.
return (
<li
onClick={handleTabClick}
className={props.name === activeName ? "active" : ""}
>
{props.heading()}
</li>
);
<Tabs>
<Tabs.Tab
name="Description"
initialActive={true}
heading={() => <b>Description</b>}
/>
<Tabs.Tab name="Reviews" heading={() => "Reviews"} />
</Tabs>
We may get a TSLint warning: Lambdas are forbidden in JSX attributes due to their rendering performance impact. It is useful to know that lambdas can be problematic so that we can keep this in mind for when we do experience a performance problem. However, we are going to switch this rule off in tslint.json by specifying "jsx-no-lambda" as false:
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"rules": {
...
"jsx-no-lambda": false
},
...
}
After we have saved the new TSLint settings, the compiler complaint will hopefully go away. Note that we may need to kill the Terminal and npm start the app again for the compiler complaint to go away.
If we try using the product page in our app, it will behave just as it did before.
So, implementing the render prop pattern is very simple. The most time-consuming thing with this pattern is understanding what it can do and how it works. Once we've got to grips with it, it is an excellent pattern we can use to provide rendering flexibility to consumers of our components.
We have just one more section to go now before our Tab component is complete.
The finish line is in sight now for our Tab component. The final task is to allow consumers to render tab content. We'll use the children prop to do this:
interface ITabsContext {
activeName: string;
handleTabClick?: (name: string, content: React.ReactNode) => void;
}
interface IState {
activeName: string;
activeContent: React.ReactNode;
}
private handleTabClick = (name: string, content: React.ReactNode) => {
this.setState({ activeName: name, activeContent: content });
};
const handleTabClick = (e: React.MouseEvent<HTMLLIElement>) => {
if (context.handleTabClick) {
context.handleTabClick(props.name, props.children);
}
};
<TabsContext.Provider ...
>
<ul className="tabs">{this.props.children}</ul>
<div>{this.state && this.state.activeContent}</div>
</TabsContext.Provider>
<h1>{product.name}</h1>
<Tabs>
<Tabs.Tab
name="Description"
initialActive={true}
heading={() => <b>Description</b>}
>
<p>{product.description}</p>
</Tabs.Tab>
<Tabs.Tab name="Reviews" heading={() => "Reviews"}>
<ul className="product-reviews">
{product.reviews.map(review => (
<li key={review.reviewer}>
<i>"{review.comment}"</i> - {review.reviewer}
</li>
))}
</ul>
</Tabs.Tab>
</Tabs>
<p className="product-price">
...
</p>
The tab content is now nested within each Tab component exactly how we wanted.
Let's give this a try. If we go to the product page we notice an issue:

The content isn't being rendered when the page first loads. If we click on the Reviews tab or the Description tab, the content then loads.
public static Tab: React.SFC<ITabProps> = props => (
<TabsContext.Consumer>
{(context: ITabsContext) => {
if (!context.activeName && props.initialActive) {
if (context.handleTabClick) {
context.handleTabClick(props.name, props.children);
return null;
}
}
const activeName = context.activeName
? context.activeName
: props.initialActive
? props.name
: "";
...
}}
</TabsContext.Consumer>
);
The highlighted lines invoke the tab click handler if there is no active tab in the context and the tab is flagged as initially active. In this case, we return null because invoking the tab click will set the state for the active tab, which will cause another rendering cycle.
Our tabs component should now be complete. Let's check by going to the product page:

The content renders as we expect. If we click on the Reviews tab, this renders fine as well:

So, the render props and children props patterns are great for allowing consumers to render custom content. The syntax may look a little tricky at first, but when you understand it, it makes perfect sense and is really elegant.
In the next section, we'll look at the final pattern in this chapter.
A higher-order component (HOC) is a functional component that takes in a component as a parameter and returns an enhanced version of that component. That may not make a lot of sense, so we're going to go through an example in this section. Our example creates a HOC called withLoader that can be applied to any component in order to add a loading spinner when the component is busy. We are going to use this in our React shop (that we worked on in the last section) in the product page whilst data is being fetched. It will look like the following when we have finished:

At the moment, the data fetching in our shop is instantaneous because all the data is local. So, before working on the withLoader component, let's refactor the data fetching functions to include a delay and be asynchronous as well. This will better simulate a real data fetching function that gets the data using a web API:
export const getProduct = async (id: number): Promise<IProduct | null> => {
await wait(1000);
const foundProducts = products.filter(customer => customer.id === id);
return foundProducts.length === 0 ? null : foundProducts[0];
};
The function takes in the product ID and uses the filter function in the products array to find the product and then returns it.
The function is prefixed with the async keyword because it is asynchronous.
const wait = (ms: number): Promise<void> => {
return new Promise(resolve => setTimeout(resolve, ms));
};
This function uses the standard JavaScript setTimeout function to wait for the number of milliseconds we specify in the function parameter. The function returns a Promise that is resolved when setTimeout completes.
So, we have a function that now fetches a product asynchronously taking at least 1 second. Let's plug this into our product page. The ProductPage component is a container component responsible for fetching data, so let's plug this in here.
import { getProduct, IProduct } from "./ProductsData";
interface IState {
product?: IProduct;
added: boolean;
loading: boolean;
}
public constructor(props: Props) {
super(props);
this.state = {
added: false,
loading: true
};
}
public async componentDidMount() {
if (this.props.match.params.id) {
const id: number = parseInt(this.props.match.params.id, 10);
const product = await getProduct(id);
if (product !== null) {
this.setState({ product, loading: false });
}
}
}
We call getProduct asynchronously using the await keyword. In order to do this, we need to mark the componentDidMount lifecycle method as asynchronous with the async keyword. After we've got the product, we set it in the state and reset the loading flag to false.
npm start
If we go to the product page, we see that it takes roughly 1 second for the product to load now. You may notice Product not found! being displayed whilst the product loads. This is because the product is not set on the initial render. We'll ignore this for the time being because our withLoader HOC will resolve this issue.
So, now that we are getting data asynchronously and roughly taking 1 second, we are ready to implement our withLoader HOC and use it on the product page. We'll do just this in the next section.
We're going to create a loader spinner component called withLoader that can be used with any component to indicate that the component is busy doing something:
import * as React from "react";
interface IProps {
loading: boolean;
}
const withLoader = <P extends object>(
Component: React.ComponentType<P>
): React.SFC<P & IProps> => ({ loading, ...props }: IProps) =>
// TODO - return a loading spinner if loading is true otherwise return the component passed in
export default withLoader;
There are a few things going on here, so let's break this down:
const withLoader = <P extends object>(
Component: React.ComponentType<P>
): React.SFC<P & IProps> => ({ loading, ...props }: IProps) =>
loading ? (
<div className="loader-overlay">
<div className="loader-circle-wrap">
<div className="loader-circle" />
</div>
</div>
) : (
<Component {...props} />
);
The component passed in is returned in the second ternary branch. We use the spread syntax to spread the properties in the props variable into the component.
The loading spinner is returned in the first ternary branch.
.loader-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: Black;
opacity: 0.3;
z-index: 10004;
}
.loader-circle-wrap {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: 100px;
width: 100px;
margin: auto;
}
.loader-circle {
border: 4px solid #ffffff;
border-top: 4px solid #899091;
border-radius: 50%;
width: 100px;
height: 100px;
animation: loader-circle-spin 0.7s linear infinite;
}
The loader-overlay class creates a black see-through overlay over the whole page. The loader-circle-wrap class creates a 100px by 100px square in the center of the overlay. The loader-circle class creates the spinning circle.
Our withLoader HOC is now complete.
For reference, a class-based version of withLoader is shown in the following code block:
const withLoader = <P extends object>(Component: React.ComponentType<P>) =>
class WithLoader extends React.Component<P & IProps> {
public render() {
const { loading, ...props } = this.props as IProps;
return loading ? (
<div className="loader-overlay">
<div className="loader-circle-wrap">
<div className="loader-circle" />
</div>
</div>
) : (
<Component {...props} />
);
}
};
We are going to stick with the SFC version, though, because it doesn't contain any state or need access to any lifecycle methods.
In the next section, we'll consume our withLoader component in the product page in our shop app.
Consuming a HOC is very simple. We simply wrap the HOC around the component that we want to enhance. The easiest place to do this is in the export statement.
Let's add the withLoader HOC we created in the previous section to our product page:
import withLoader from "./withLoader";
export default withLoader(Product);
We now get a compilation error in the ProductPage component because it expects to pass Product a loading property.
<Product
loading={this.state.loading}
product={product}
inBasket={this.state.added}
onAddToBasket={this.handleAddClick}
/>
{product || this.state.loading ? (
<Product
loading={this.state.loading}
product={product}
inBasket={this.state.added}
onAddToBasket={this.handleAddClick}
/>
) : (
<p>Product not found!</p>
)}
This gives us another compilation error, though, because the product property within the Product component doesn't expect to be undefined. However, it will be undefined when the product is being loaded.
interface IProps {
product?: IProduct;
inBasket: boolean;
onAddToBasket: () => void;
}
This then gives further compilation errors in the JSX in the Product component where we reference the product property because it now will be undefined during the loading of the data.
const handleAddClick = () => {
props.onAddToBasket();
};
if (!product) {
return null;
}
return (
<React.Fragment>
...
</React.Fragment>
);
Now that the TypeScript compiler is happy, if we go to the product page in our shop it will display our loading spinner before rendering the product:

So, HOCs are great for enhancing components where the enhancement is something that can be applied to many components. Our loader spinner is a common use case for a HOC. Another very common usage of the HOC pattern is when using React Router. We used the React Router withRouter HOC previously in this book to access parameters for a path.
In this chapter, we learned about container components and how they can be used to manage state and what presentational components need to do. Presentational components can then focus on what they need to look like. This allows presentational components to be more easily reused in multiple places and unit-tested.
We learned that compound components are components that rely on each other. Declaring compound children as static members on the parent class make it clear to a consumer that the components should be used together. React context is a convenient way for compound components to share their state.
We learned about the special children prop that can be used to access and render a component's children. We then learned that we can create our own render props to give consumers great flexibility for custom-rendering sections of a component.
In the last section, we learned about higher-order components and how they can be used to implement common enhancements to components. We already consumed the React Router higher-order component when gaining access to a paths parameters earlier in the book.
In the next chapter, we'll learn how we create forms in React. Towards the end of the next chapter, we'll use some of these patterns that we have learned in this chapter in order to deal with forms in a generic way.
Let's put what we have learned about component patterns to the test with some questions:
export const getProducts = async (): Promise<IProduct[]> => {
await wait(1000);
return products;
};
Can you use this to implement a loader spinner on the products page by consuming the withLoader HOC?
<Loader loading={this.state.loading}>
<div>
The content for my component ...
</div>
</Loader>
If so, have a go at implementing it.
Forms are extremely common in the apps we build. In this chapter, we'll learn how to build forms using controlled components in React and TypeScript. We'll build a Contact Us form for the React shop we have been working on in other chapters as our learning exercise.
We'll quickly discover that there is a fair amount of boilerplate code involved in creating forms, so we'll look at building a generic form component to reduce the boilerplate code. Client-side validation is critical to the user experience of the forms we build, so we'll also cover this topic in a fair amount of depth.
Finally, form submission is a critical consideration. We'll cover how to handle submission errors, as well as success.
In this chapter, we'll discuss the following topics:
We'll use the following technologies in this chapter:
Forms are a common part of most apps. In React, the standard way to create a form is with what is called controlled components. A controlled component has its value synchronized with state in React. This will make more sense when we've implemented our first controlled component.
We are going to extend the React shop we have been building to include a Contact Us form. This will be implemented using controlled components.
Before we start work on our form, we need a page to host the form in. The page will be a container component, and our form will be a presentational component. We also need to create a navigation option that takes us to our new page.
We'll write the following codes before starting to implement our form:
import * as React from "react";
class ContactUsPage extends React.Component {
public render() {
return (
<div className="page-container">
<h1>Contact Us</h1>
<p>
If you enter your details we'll get back to you as soon as
we can.
</p>
</div>
);
}
}
export default ContactUsPage;
This component will eventually contain state, so, we have created a class-based component. This simply renders a heading with some instructions at the moment. Eventually, it will reference our form.
import ContactUsPage from "./ContactUsPage";
<Switch>
<Redirect exact={true} from="/" to="/products" />
<Route path="/products/:id" component={ProductPage} />
<Route exact={true} path="/products" component={ProductsPage} />
<Route path="/contactus" component={ContactUsPage} />
<Route path="/admin">
...
</Route>
<Route path="/login" component={LoginPage} />
<Route component={NotFoundPage} />
</Switch>
<nav>
<NavLink to="/products" className="header-link" activeClassName="header-link-active">
Products
</NavLink>
<NavLink to="/contactus" className="header-link" activeClassName="header-link-active">
Contact Us
</NavLink>
<NavLink to="/admin" className="header-link" activeClassName="header-link-active">
Admin
</NavLink>
</nav>
npm start
You should see a new navigation option that takes us to our new page:

Now that we have our new page, we are ready to implement our first controlled input in a form. We'll do this in the following section.
In this section, we'll start to create our form containing our first controlled input:
import * as React from "react";
const ContactUs: React.SFC = () => {
return (
<form className="form" noValidate={true}>
<div className="form-group">
<label htmlFor="name">Your name</label>
<input type="text" id="name" />
</div>
</form>
);
};
export default ContactUs;
This is a function component that renders a form containing a label and an input for the user's name.
.form {
width: 300px;
margin: 0px auto 0px auto;
}
.form-group {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.form-group label {
align-self: flex-start;
font-size: 16px;
margin-bottom: 3px;
}
.form-group input, select, textarea {
font-family: Arial;
font-size: 16px;
padding: 5px;
border: lightgray solid 1px;
border-radius: 5px;
}
The form-group class wraps each field in our form, displaying the label above the input with nice spacing.
import ContactUs from "./ContactUs";
<div className="page-container">
<h1>Contact Us</h1>
<p>If you enter your details we'll get back to you as soon as we can.</p>
<ContactUs />
</div>
If we look at the running app and go to the Contact Us page, we'll see our name field rendered:

We can enter our name into this field, but nothing will happen yet. We want the entered name to be stored in the ContactUsPage container component state. This is because ContactUsPage will eventually manage the form submission.
interface IState {
name: string;
email: string;
reason: string;
notes: string;
}
class ContactUsPage extends React.Component<{}, IState> { ... }
As well as the person's name, we are going to capture their email address, reason for contacting the shop, and any additional notes.
public constructor(props: {}) {
super(props);
this.state = {
email: "",
name: "",
notes: "",
reason: ""
};
}
interface IProps {
name: string;
email: string;
reason: string;
notes: string;
}
const ContactUs: React.SFC<IProps> = props => { ... }
We have created props for all the data we are going to eventually capture in our form.
<div className="form-group">
<label htmlFor="name">Your name</label>
<input type="text" id="name" value={props.name} />
</div>
<ContactUs
name={this.state.name}
email={this.state.email}
reason={this.state.reason}
notes={this.state.notes}
/>
Let's go to the running app and go to our Contact Us page. Try typing something into the name input.
Nothing seems to happen... something is preventing us from entering the value.
We have just set the input value to some React state, so React is now controlling the value of the input. This is why we no longer appear to be able to type into it.
We are part-way through creating our first controlled input. However, controlled inputs aren't much use if users can't enter anything into them. So, how can we make our input editable again?
The answer is that we need to listen to changes to the input value, and update the state accordingly. React will then render the new input value from the state.
<input type="text" id="name" value={props.name} onChange={handleNameChange} />
const ContactUs: React.SFC<IProps> = props => {
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
props.onNameChange(e.currentTarget.value);
};
return ( ... );
};
Note that we've used the generic React.ChangeEvent command with the type of the element we are handling (HTMLInputElement).
The currentTarget prop in the event argument gives us a reference to the element that the event handler is attached to. The value property within this gives us the latest value of the input.
interface IProps {
name: string;
onNameChange: (name: string) => void;
email: string;
onEmailChange: (email: string) => void;
reason: string;
onReasonChange: (reason: string) => void;
notes: string;
onNotesChange: (notes: string) => void;
}
<ContactUs
name={this.state.name}
onNameChange={this.handleNameChange}
email={this.state.email}
onEmailChange={this.handleEmailChange}
reason={this.state.reason}
onReasonChange={this.handleReasonChange}
notes={this.state.notes}
onNotesChange={this.handleNotesChange}
/>
private handleNameChange = (name: string) => {
this.setState({ name });
};
private handleEmailChange = (email: string) => {
this.setState({ email });
};
private handleReasonChange = (reason: string) => {
this.setState({ reason });
};
private handleNotesChange = (notes: string) => {
this.setState({ notes });
};
If we now go to the Contact Us page in the running app and enter something into the name, this time the input behaves as expected.
<form className="form" noValidate={true} onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="name">Your name</label>
<input type="text" id="name" value={props.name} onChange={handleNameChange} />
</div>
<div className="form-group">
<label htmlFor="email">Your email address</label>
<input type="email" id="email" value={props.email} onChange={handleEmailChange} />
</div>
<div className="form-group">
<label htmlFor="reason">Reason you need to contact us</label>
<select id="reason" value={props.reason} onChange={handleReasonChange}>
<option value="Marketing">Marketing</option>
<option value="Support">Support</option>
<option value="Feedback">Feedback</option>
<option value="Jobs">Jobs</option>
<option value="Other">Other</option>
</select>
</div>
<div className="form-group">
<label htmlFor="notes">Additional notes</label>
<textarea id="notes" value={props.notes} onChange={handleNotesChange} />
</div>
</form>
For each field, we render a label and the appropriate editor inside a div container, with a form-group class to space our fields out nicely.
All the editors reference handlers for handling changes to their value. All the editors also have their value set from the appropriate ContactUs prop. So, all the field editors have controlled components.
Let's have a closer look at the select editor. We set the value in the select tag using a value attribute. However, this doesn't exist in the native select tag. Usually, we have to include a selected attribute in the relevant option tag within the select tag:
<select id="reason">
<option value="Marketing">Marketing</option>
<option value="Support" selected>Support</option>
<option value="Feedback">Feedback</option>
<option value="Jobs">Jobs</option>
<option value="Other">Other</option>
</select>
React adds the value prop to the select tag, and manages the selected attribute on the option tag for us, behind the scenes. This allows us to consistently manage input, textarea, and selected in our code.
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
props.onEmailChange(e.currentTarget.value);
};
const handleReasonChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
props.onReasonChange(e.currentTarget.value);
};
const handleNotesChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
props.onNotesChange(e.currentTarget.value);
};
This completes our basic Contact Us form, using various controlled form elements. We haven't implemented any validation or submitted the form yet. We'll get to these later in the chapter.
We're already noticing lots of similar code for each field for getting changes to fields into state. In the next section, we are going to start work on a generic form component and switch to using this for our Contact Us form.
Generic form components will help reduce the amount of code required to implement a form. We are going to do just this in this section, refactoring what we did in the last section for our ContactUs component.
Let's think about how we would ideally consume the generic components to produce the new version of the ContactUs component. It could be something like the following JSX:
<Form
defaultValues={{ name: "", email: "", reason: "Support", notes: "" }}
>
<Form.Field name="name" label="Your name" />
<Form.Field name="email" label="Your email address" type="Email" />
<Form.Field name="reason" label="Reason you need to contact us" type="Select" options={["Marketing", "Support", "Feedback", "Jobs", "Other"]} />
<Form.Field name="notes" label="Additional notes" type="TextArea" />
</Form>
In this example, there are two generic compound components: Form and Field. Here are some key points:
The JSX to render the new ContactUs component is much shorter than the original version, and arguably easier to read. The state management and event handlers are hidden away and encapsulated within the Form component.
It's time to start work on our generic Form component:
import * as React from "react";
interface IFormProps {}
interface IState {}
export class Form extends React.Component<IFormProps, IState> {
constructor(props: IFormProps) {}
public render() {}
}
Form is a class-based component because it needs to manage state. We have named the props interface IFormProps because later on we'll need an interface for field props.
export interface IValues {
[key: string]: any;
}
interface IFormProps {
defaultValues: IValues;
}
We use an additional interface called IValues for the default value type. This is an indexable key/value type that has a string type key and an any type value. The key will be the field name, and the value will be the field value.
So, the value for the defaultValues prop could be this:
{ name: "", email: "", reason: "Support", notes: "" }
interface IState {
values: IValues;
}
Note that this is the same type as the defaultValues prop, which is IValues.
constructor(props: IFormProps) {
super(props);
this.state = {
values: props.defaultValues
};
}
public render() {
return (
<form className="form" noValidate={true}>
{this.props.children}
</form>
);
}
We render the child components in a form tag, using the magical children prop we used in the last chapter.
This leads us nicely to the Field component, which we'll implement in the next section.
The Field component needs to render a label and an editor. It will live in a static property called Field inside the Form component. Consumers can then reference this component using Form.Field:
interface IFieldProps {
name: string;
label: string;
type?: "Text" | "Email" | "Select" | "TextArea";
options?: string[];
}
public static Field: React.SFC<IFieldProps> = props => {
return ();
};
Form.Field.defaultProps = {
type: "Text"
};
So, the default type will be a text-based input.
public static Field: React.SFC<IFieldProps> = props => {
const { name, label, type, options } = props;
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<input type={type.toLowerCase()} id={name} />
</div>
);
}
This is a good start, but not all the different field editors are inputs. In fact, this will only work for types Text and Email.
<label htmlFor={name}>{label}</label>
{(type === "Text" || type === "Email") && (
<input type={type.toLowerCase()} id={name} />
)}
{(type === "Text" || type === "Email") ... }
{type === "TextArea" && (
<textarea id={name} />
)}
{type === "TextArea" ... }
{type === "Select" && (
<select>
{options &&
options.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
)}
We render a select tag, containing the options specified by using the map function in the options array prop. Note that we give each option a unique key attribute to keep React happy when detecting any changes to the options.
We now have basic Form and Field components in play, which is great. However, the implementation is still pretty useless because we are not managing the field values yet in state. Let's cover that in the next section.
The state for field values lives in the Form component. However, the values are rendered and changed with the Field component. The Field component doesn't have access to the state within Form, because the state lives in the Form instance and Field doesn't.
This is very similar to the compound Tabs component we implemented in the last chapter. We shared state between the components in the Tabs compound using React context.
We are going to use the same approach for our Forms component in this section:
interface IFormContext {
values: IValues;
}
The context just contains values that have the same type, IValues, as in our state.
const FormContext = React.createContext<IFormContext>({
values: {}
});
We keep the TypeScript compiler happy by setting the initial context value to an empty literal value.
public render() {
const context: IFormContext = {
values: this.state.values
};
return ( ... )
}
<FormContext.Provider value={context}>
<form ... >
...
</form>
</FormContext.Provider>
<FormContext.Consumer>
{context => (
<div className="form-group">
</div>
)}
</FormContext.Consumer>
<div className="form-group">
<label htmlFor={name}>{label}</label>
{(type === "Text" || type === "Email") && (
<input type={type.toLowerCase()} id={name} value={context.values[name]} />
)}
{type === "TextArea" && (
<textarea id={name} value={context.values[name]} />
)}
{type === "Select" && (
<select value={context.values[name]}>
...
</select>
)}
</div>
The TypeScript compiler is now happy with our Form and Field components. So, we could start work on the new ContactUs implementation.
However, users will not be able to enter anything into our form yet, because we are not handling changes and passing new values to state. We now need to implement change handlers.
private setValue = (fieldName: string, value: any) => {
const newValues = { ...this.state.values, [fieldName]: value };
this.setState({ values: newValues });
};
Here are the key points in this method:
interface IFormContext {
values: IValues;
setValue?: (fieldName: string, value: any) => void;
}
We set the property as optional to keep the TypeScript compiler happy when the form context component is created.
const context: IFormContext = {
setValue: this.setValue,
values: this.state.values
};
const { name, label, type, options } = props;
const handleChange = (
e:
| React.ChangeEvent<HTMLInputElement>
| React.ChangeEvent<HTMLTextAreaElement>
| React.ChangeEvent<HTMLSelectElement>,
context: IFormContext
) => {
if (context.setValue) {
context.setValue(props.name, e.currentTarget.value);
}
};
Let's look at the key points in this method:
<input
type={type.toLowerCase()}
id={name}
value={context.values[name]}
onChange={e => handleChange(e, context)}
/>
Note that we use a lamda function so that we can pass in the context value to handleChange.
<textarea
id={name}
value={context.values[name]}
onChange={e => handleChange(e, context)}
/>
<select
value={context.values[name]}
onChange={e => handleChange(e, context)}
>
...
</select>
So, our Form and Field components are now nicely working together, rendering fields and managing their values. In the next section, we'll give our generic components a try by implementing a new ContactUs component.
In this section, we are going to implement a new ContactUs component using our Form and Field components:
const ContactUs: React.SFC = () => {
return ();
};
import { Form } from "./Form";
return (
<Form
defaultValues={{ name: "", email: "", reason: "Support", notes: "" }}
>
</Form>
);
<Form
defaultValues={{ name: "", email: "", reason: "Support", notes: "" }}
>
<Form.Field name="name" label="Your name" />
</Form>
Note we haven't passed the type prop because this will default to a text-based input, which is just what we require.
<Form
defaultValues={{ name: "", email: "", reason: "Support", notes: "" }}
>
<Form.Field name="name" label="Your name" />
<Form.Field name="email" label="Your email address" type="Email" />
<Form.Field
name="reason"
label="Reason you need to contact us"
type="Select"
options={["Marketing", "Support", "Feedback", "Jobs", "Other"]}
/>
<Form.Field name="notes" label="Additional notes" type="TextArea" />
</Form>
class ContactUsPage extends React.Component<{}, {}> {
public render() {
return (
<div className="page-container">
<h1>Contact Us</h1>
<p>
If you enter your details we'll get back to you as soon as we can.
</p>
<ContactUs />
</div>
);
}
}
If we go to the running app and go to the Contact Us page, it renders as required and accepts the values we enter.
Our generic form component is progressing nicely, and we have consumed it to implement the ContactUs component as we had hoped. In the next section, we are going to improve our generic component even further by adding validation.
Including validation on a form improves the user experience, by giving them immediate feedback on whether the information entered is valid. In this section, we are going to add validation to our Form component and then consume it in our ContactUs component.
The validation rules we are going to implement in the ContactUs component are these:
We are going to execute validation rules when the field editor loses focus.
In the next section, we'll add a prop to the Form component that allows consumers to specify validation rules.
Let's think about how we would want to specify validation rules to a form. We need to be able to specify one or more rules for a field. Some rules could have a parameter, such as a minimum length. It would be nice if we could specify the rules, as in the example that follows:
<Form
...
validationRules={{
email: { validator: required },
name: [{ validator: required }, { validator: minLength, arg: 3 }]
}}
>
...
</Form>
Let's have a go at implementing the validationRules prop on the Form component:
export type Validator = (
fieldName: string,
values: IValues,
args?: any
) => string;
A Validator function will take in the field name, the values for the whole form, and an optional argument specific to the function. A string containing the validation error message will be returned. If the field is valid, a blank string will be returned.
export const required: Validator = (
fieldName: string,
values: IValues,
args?: any
): string =>
values[fieldName] === undefined ||
values[fieldName] === null ||
values[fieldName] === ""
? "This must be populated"
: "";
We export the function so that it can be used in our ContactUs implementation later. The function checks whether the field value is undefined, null, or an empty string and if so, it returns a This must be populated validation error message.
If the field value isn't undefined, null, or an empty string, then an empty string is returned to indicate the value is valid.
export const minLength: Validator = (
fieldName: string,
values: IValues,
length: number
): string =>
values[fieldName] && values[fieldName].length < length
? `This must be at least ${length} characters`
: "";
The function checks whether the length of the field value is less than the length argument, and if so it returns a validation error message. Otherwise, an empty string is returned to indicate the value is valid.
interface IValidation {
validator: Validator;
arg?: any;
}
interface IValidationProp {
[key: string]: IValidation | IValidation[];
}
interface IFormProps {
defaultValues: IValues;
validationRules: IValidationProp;
}
import { Form, minLength, required } from "./Form";
<Form
defaultValues={{ name: "", email: "", reason: "Support", notes: "" }}
validationRules={{
email: { validator: required },
name: [{ validator: required }, { validator: minLength, arg: 2 }]
}}
>
...
</Form>
Now, our form is valid if the name and email are populated, and the name is at least two characters long.
That's the validationRules prop complete. In the next section, we'll track the validation error messages in preparation for rendering them on the page.
We need to track the validation error messages in state as the user completes the form and fields become valid or invalid. Later on, we'll be able to render the error messages to the screen.
The Form component is responsible for managing all the form states, so we'll add the error message state to there, as follows:
interface IErrors {
[key: string]: string[];
}
interface IState {
values: IValues;
errors: IErrors;
}
The errors state is an indexable key/value type where the key is the field name and the value is an array of validation error messages.
constructor(props: IFormProps) {
super(props);
const errors: IErrors = {};
Object.keys(props.defaultValues).forEach(fieldName => {
errors[fieldName] = [];
});
this.state = {
errors,
values: props.defaultValues
};
}
The defaultValues prop contains all the field names in its keys. We iterate through the defaultValues keys, setting the appropriate errors key to an empty array. As a result, when the Form component initializes, none of the fields contain any validation error messages, which is exactly what we want.
interface IFormContext {
errors: IErrors;
values: IValues;
setValue?: (fieldName: string, value: any) => void;
}
const FormContext = React.createContext<IFormContext>({
errors: {},
values: {}
});
public render() {
const context: IFormContext = {
errors: this.state.errors,
setValue: this.setValue,
values: this.state.values
};
return (
...
);
}
Now, the validation errors are in the form state, and also in the form context for the Field component to access. In the next section, we'll create a method that is going to invoke the validation rules.
So far, we can define validation rules, and have state to track validation error messages, but nothing is invoking the rules yet. This is what we are going to implement in this section:
private validate = (
fieldName: string,
value: any
): string[] => {
};
private validate = (
fieldName: string,
value: any
): string[] => {
const rules = this.props.validationRules[fieldName];
const errors: string[] = [];
// TODO - execute all the validators
return errors;
}
const errors: string[] = [];
if (Array.isArray(rules)) {
// TODO - execute all the validators in the array of rules
} else {
if (rules) {
const error = rules.validator(fieldName, this.state.values, rules.arg);
if (error) {
errors.push(error);
}
}
}
return errors;
if (Array.isArray(rules)) {
rules.forEach(rule => {
const error = rule.validator(
fieldName,
this.state.values,
rule.arg
);
if (error) {
errors.push(error);
}
});
} else {
...
}
return errors;
if (Array.isArray(rules)) {
...
} else {
...
}
const newErrors = { ...this.state.errors, [fieldName]: errors };
this.setState({ errors: newErrors });
return errors;
We spread the old errors state into a new object, and then add the new errors for the field.
interface IFormContext {
values: IValues;
errors: IErrors;
setValue?: (fieldName: string, value: any) => void;
validate?: (fieldName: string, value: any) => void;
}
public render() {
const context: IFormContext = {
errors: this.state.errors,
setValue: this.setValue,
validate: this.validate,
values: this.state.values
};
return (
...
);
}
Our form validation is coming along nicely, and we now have a method we can call to invoke all the rules for a field. However, this method isn't being called from anywhere yet as the user fills out the form. We'll do that in the next section.
When the user fills in the form, we want the validation rules to trigger when a field loses focus. We'll implement this in this section:
const handleChange = (
...
};
const handleBlur = (
e:
| React.FocusEvent<HTMLInputElement>
| React.FocusEvent<HTMLTextAreaElement>
| React.FocusEvent<HTMLSelectElement>,
context: IFormContext
) => {
if (context.validate) {
context.validate(props.name, e.currentTarget.value);
}
};
return ( ... )
{(type === "Text" || type === "Email") && (
<input
type={type.toLowerCase()}
id={name}
value={context.values[name]}
onChange={e => handleChange(e, context)}
onBlur={e => handleBlur(e, context)}
/>
)}
We set the onBlur prop to a lamda expression that calls our handleBlur function, passing in the blur argument as well as the context value.
{type === "TextArea" && (
<textarea
id={name}
value={context.values[name]}
onChange={e => handleChange(e, context)}
onBlur={e => handleBlur(e, context)}
/>
)}
{type === "Select" && (
<select
value={context.values[name]}
onChange={e => handleChange(e, context)}
onBlur={e => handleBlur(e, context)}
>
...
</select>
)}
Our field is now executing validation rules when it loses focus. There's one more task to do before we can give our Contact Us page a try, which we'll do in the next section.
In this section, we are going to render the validation error messages in the Field component:
<div className="form-group">
<label htmlFor={name}>{label}</label>
{(type === "Text" || type === "Email") && (
...
)}
{type === "TextArea" && (
...
)}
{type === "Select" && (
...
)}
{context.errors[name] &&
context.errors[name].length > 0 &&
context.errors[name].map(error => (
<span key={error} className="form-error">
{error}
</span>
))}
</div>
So, we first check that we have errors for the field name, and then use the map function in the errors array to render a span for each error.
.form-error {
font-size: 13px;
color: red;
margin: 3px auto 0px 0px;
}
It's time to give the Contact Us page a try. If our app isn't started, start it using npm start and go to the Contact Us page. If we tab through the name and email fields, the required validation rule triggers and error messages are displayed:

This is just what we want. If we go back to the name field and try to enter just a single character before tabbing away, the minimum length validation error triggers, as we would expect:

Our generic form component is nearly complete now. Our final task is to submit the form, which we'll do in the next section.
Submitting the form is the final part of the form implementation. The consumer of the Form component will handle the actual submission, which will probably result in a call to a web API. Our Form component will simply call a function in the consumer code when the form is submitted.
In this section, we are going to add a submit button to our Form component:
<FormContext.Provider value={context}>
<form className="form" noValidate={true}>
{this.props.children}
<div className="form-group">
<button type="submit">Submit</button>
</div>
</form>
</FormContext.Provider>
.form-group button {
font-size: 16px;
padding: 8px 5px;
width: 80px;
border: black solid 1px;
border-radius: 5px;
background-color: black;
color: white;
}
.form-group button:disabled {
border: gray solid 1px;
background-color: gray;
cursor: not-allowed;
}
We now have a black submit button on our form that is gray when disabled.
In our Form component, we need a new prop that allows a consumer to specify the submit function to be called. We'll do this in this section:
export interface ISubmitResult {
success: boolean;
errors?: IErrors;
}
interface IFormProps {
defaultValues: IValues;
validationRules: IValidationProp;
onSubmit: (values: IValues) => Promise<ISubmitResult>;
}
The function will take in the field values and asynchronously return whether the submission was successful, with any validation errors that occurred on the server.
interface IState {
values: IValues;
errors: IErrors;
submitting: boolean;
submitted: boolean;
}
constructor(props: IFormProps) {
...
this.state = {
errors,
submitted: false,
submitting: false,
values: props.defaultValues
};
}
<button
type="submit"
disabled={this.state.submitting || this.state.submitted}
>
Submit
</button>
<form className="form" noValidate={true} onSubmit={this.handleSubmit}>
...
</form>
private handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
We call preventDefault in the submit event argument to stop the browser automatically posting the form.
private validateForm(): boolean {
const errors: IErrors = {};
let haveError: boolean = false;
Object.keys(this.props.defaultValues).map(fieldName => {
errors[fieldName] = this.validate(
fieldName,
this.state.values[fieldName]
);
if (errors[fieldName].length > 0) {
haveError = true;
}
});
this.setState({ errors });
return !haveError;
}
private handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (this.validateForm()) {
}
};
The validateForm function iterates through the fields, calling the validate function that has already been implemented. The state is updated with the latest validation errors, and we return whether there are any errors or not in any of the fields.
private handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (this.validateForm()) {
this.setState({ submitting: true });
const result = await this.props.onSubmit(this.state.values);
this.setState({
errors: result.errors || {},
submitted: result.success,
submitting: false
});
}
};
If the form is valid, we start by setting the submitting state to true. We then call the onSubmit prop function asynchronously. When the onSubmit prop function has finished, we set any validation errors from the function in the state along with whether the submission was successful. We also set in the state the fact that the submission process has finished.
Now, our Form component has an onSubmit function prop. In the next section, we'll consume this in our Contact Us page.
In this section, we'll consume the onSubmit form prop in the ContactUs component. The ContactUs component won't manage the submission—it will simply delegate to the ContactUsPage component to handle the submission:
import { Form, ISubmitResult, IValues, minLength, required } from "./Form";
interface IProps {
onSubmit: (values: IValues) => Promise<ISubmitResult>;
}
const ContactUs: React.SFC<IProps> = props => { ... }
const ContactUs: React.SFC<IProps> = props => {
const handleSubmit = async (values: IValues): Promise<ISubmitResult> => {
const result = await props.onSubmit(values);
return result;
};
return ( ... );
};
The onSubmit prop is asynchronous, so we need to prefix our function with async and prefix the onSubmit call with await.
return (
<Form ... onSubmit={handleSubmit}>
...
</Form>
);
private handleSubmit = async (values: IValues): Promise<ISubmitResult> => {
await wait(1000); // simulate asynchronous web API call
return {
errors: {
email: ["Some is wrong with this"]
},
success: false
};
};
In practice, this will probably call a web API. In our example, we wait asynchronously for one second and return a validation error with the email field.
const wait = (ms: number): Promise<void> => {
return new Promise(resolve => setTimeout(resolve, ms));
};
<ContactUs onSubmit={this.handleSubmit} />
import { ISubmitResult, IValues } from "./Form";
If we go to the Contact Us page in the running app, fill out the form, and click the Submit button, we are informed that there is a problem with the email field, as we would expect:

private handleSubmit = async (values: IValues): Promise<ISubmitResult> => {
await wait(1000); // simulate asynchronous web API call
return {
success: true
};
};
Now, if we go to the Contact Us page in the running app again, fill out the form, and click the Submit button, the submission goes through fine and the Submit button is disabled:

So, that's our Contact Us page complete, together with our generic Form and Field components.
In this chapter, we discussed controlled components, which are React's recommended method for handling form data entry. With controlled components, we let React control input values via component state.
We looked at building generic Form and Field components containing state and change handlers, so that we don't need to implement individual state and change handlers for every field in every form in our apps.
We then created some standard validation functions, and added the ability to add validation rules within the generic Form component and render validation errors automatically in the Field component.
Finally, we added the ability to handle form submission when consuming the generic Form component. Our Contact Us page was changed to use the generic Form and Field components.
Our generic components only deal with very simple forms. Not surprisingly, there are a fair number of well-established form libraries already out in the wild. A popular choice is Formik, which is similar in some ways to what we have just built but much more powerful.
If you are building an app that contains lots of forms, it is well worth either building a generic form as we have just done or using an established library such as Formik to speed up the development process.
Check whether all that information about forms in React and TypeScript has stuck by trying the following implementations:
The following links are good sources of further information on forms in React:
So far in this book, we have managed state within our React components. We've also used React context when state needs to be shared between different components. This approach works well for many applications. React Redux helps us to robustly handle complex state scenarios. It shines when user interactions result in several changes to state, perhaps some that are conditional, and particularly when the interaction results in web service calls. It's also great when there is lots of shared state across the application.
We are going to continue building our React shop in this chapter, adding React Redux to help us manage our state interactions. We'll eventually add a basket summary component in the header of our shop, which informs the user of how many items are in their basket. Redux will help us update this component when items are added to the basket.
In the final section of the chapter, we'll explorer a Redux-like method for managing complex state within a component. This is a middle ground between managing state in a Redux store and just within a component using setState or useState.
In this chapter, we'll learn the following topics:
We'll use the following technologies in this chapter:
In this section, we'll start by going through the three principles in Redux and then dive into the core concepts.
Let's take a look at the three principles of Redux:
In the following sections, we'll dive into actions and reducers a little more along with the thing that manages them which is what is called a store.
The whole state of the application lives inside what is called a store. The state is stored in a JavaScript object like the one following:
{
products: [{ id: 1, name: "Table", ...}, {...}, ...],
productsLoading: false,
currentProduct: { id: 2, xname: "Chair", ... },
basket: [{ product: { id: 2, xname: "Chair" }, quantity: 1 }],
};
In this example, the single object contains these:
The state won't contain any functions or setters or any getters. It's a simple JavaScript object. The store also orchestrates all the moving parts in Redux. This includes pushing actions though reducers to update state.
So, the first thing that needs to happen in order to update state in a store is to dispatch an action. An action is another simple JavaScript object like the one following:
{
type: "PRODUCTS/LOADING"
}
The type property determines the type of action that needs to be performed. This is an important and required part of the action. The reducer won't know how to change the state without the type in the action object. In the previous example, the action doesn't contain anything else other than the type property. This is because the reducer doesn't need any more information in order to make the change to state for this type of action.
The following example is another action:
{
type: "PRODUCTS/GETSINGLE",
product: { id: 1, name: "Table", ...}
}
This time, an additional bit of information is included in the action in a product property. This additional information is needed by the reducer to make the change to state for this type of action.
So, reducers are pure functions that make the actual state changes.
The following is an example of a reducer:
export const productsReducer = (state = initialProductState, action) => {
switch (action.type) {
case "PRODUCTS/LOADING": {
return {
...state,
productsLoading: true
};
}
case "PRODUCTS/GETSINGLE": {
return {
...state,
currentProduct: action.product,
productsLoading: false
};
}
default:
}
return state || initialProductState;
};
Here is something about reducers:
You'll notice that the actions and reducer we have just seen didn't have TypeScript types. Obviously, we'll include the necessary types when we implement these in the following sections.
So, now that we have started to get an understanding of what Redux is, it's time to put this into practice in our React shop.
Before we can use Redux, we need to install it along with the TypeScript types. We will also install an additional library called redux-thunk, which we need in order to implement asynchronous actions:
npm install redux
Note that the core Redux library contains TypeScript types within it. So, there is no need for an additional install for these.
npm install react-redux
npm install --save-dev @types/react-redux
npm install redux-thunk
npm install --save-dev @types/redux-thunk
With all the Redux bits now installed, we can add Redux to the React shop we have been working on in the next section.
We are going to extend the React shop we have been building in previous chapters and add Redux to manage the state on the Products page. In this section, we'll create actions to start the process of getting the products into the page. There will be one action to get the products. There will be another action to change some new loading state, which we'll eventually tie to the withLoading HOC that we already have in our project.
Before we make a start on the Redux actions, let's create a fake API in ProductsData.ts for fetching products:
export const getProducts = async (): Promise<IProduct[]> => {
await wait(1000);
return products;
};
So, the function asynchronously waits a second before returning the products.
We need to start our action's implementation by creating some types. We'll do this next.
It's time to finally make a start on enhancing our React shop with Redux. We'll start by creating some types for the state and actions for our Redux store:
import { IProduct } from "./ProductsData";
export enum ProductsActionTypes {
GETALL = "PRODUCTS/GETALL",
LOADING = "PRODUCTS/LOADING"
}
Redux doesn't dictate the format of the action type strings. So, the format of the action type strings is our choice. We need to make sure the strings are unique though across the actions types in the store. So, we've included two bits of information in the string:
We could have chosen PRODUCTS-GETALL or Get All Products. We just need to make sure the strings are unique. We have used an enumeration to give us nice IntelliSense when we consume these when implementing the action and reducer.
export interface IProductsGetAllAction {
type: ProductsActionTypes.GETALL,
products: IProduct[]
}
export interface IProductsLoadingAction {
type: ProductsActionTypes.LOADING
}
The IProductsGetAllAction interface is for an action that will be dispatched when the products need to be fetched. The IProductsLoadingAction interface is for an action that will cause the reducer to change the loading state.
export type ProductsActions =
| IProductsGetAllAction
| IProductsLoadingAction
This will be the type for the action parameter passed into the reducer.
export interface IProductsState {
readonly products: IProduct[];
readonly productsLoading: boolean;
}
So, our state will contain an array of products, and whether products are being loaded.
Notice that the properties are prefixed with the readonly keyword. This will help us avoid changing the state directly.
Now that we have types in place for the actions and state, we can create some actions in the next section.
In this section, we are going to create two actions for getting the products and indicating that products are being loaded.
import { ActionCreator, AnyAction, Dispatch } from "redux";
These are a few types from Redux that we are going to use when implementing our actions.
import { ThunkAction } from "redux-thunk";
import { getProducts as getProductsFromAPI } from "./ProductsData";
We've renamed the API function to getProductsFromAPI to avoid a name clash because we are going to create an action called getProducts a little later.
import { IProductsGetAllAction, IProductsLoadingAction, IProductsState, ProductsActionTypes } from "./ProductsTypes";
const loading: ActionCreator<IProductsLoadingAction> = () => {
return {
type: ProductsActionTypes.LOADING
}
};
We can write this function more succinctly using an implicit return statement as follows:
const loading: ActionCreator<IProductsLoadingAction> = () => ({
type: ProductsActionTypes.LOADING
});
We'll use this shorter syntax from now on when implementing action creators.
export const getProducts: ActionCreator<ThunkAction<Promise<AnyAction>, IProductsState, null, IProductsGetAllAction>> = () => {};
We again use the generic ActionCreator type, but this time it contains more than just the action interface that will eventually be returned. This is because this particular action is asynchronous.
We use ThunkAction inside ActionCreator for asynchronous actions, which is, in turn, a generic type with four parameters:
We export this action creator because this is going to be eventually called from the ProductsPage component.
export const getProducts: ActionCreator<ThunkAction<Promise<AnyAction>, IProductsState, null, IProductsGetAllAction>> = () => {
return async (dispatch: Dispatch) => {
};
};
So, the first thing that function does is return another function, flagging that it is asynchronous, using the async keyword. The inner function takes the dispatcher from the store as a parameter.
return async (dispatch: Dispatch) => {
dispatch(loading());
const products = await getProductsFromAPI();
return dispatch({
products,
type: ProductsActionTypes.GETALL
});
};
Now that we have created a couple of actions, we'll create a reducer in the next section.
A reducer is a function that is responsible for creating new state for a given action. So, the function takes in an action with the current state and returns the new state. In this section, we'll create a reducer for the two actions we have created on products.
import { Reducer } from "redux";
import { IProductsState, ProductsActions, ProductsActionTypes } from "./ProductsTypes";
We are importing the Reducer type from Redux along with types for the actions and state we created earlier.
const initialProductState: IProductsState = {
products: [],
productsLoading: false
};
So, we are setting the products to an empty array and product loading state to false.
export const productsReducer: Reducer<IProductsState, ProductsActions> = (
state = initialProductState,
action
) => {
switch (action.type) {
// TODO - change the state
}
return state;
};
switch (action.type) {
case ProductsActionTypes.LOADING: {
return {
...state,
productsLoading: true
};
}
case ProductsActionTypes.GETALL: {
return {
...state,
products: action.products,
productsLoading: false
};
}
}
We have implemented a switch branch for each action. Both branches follow the same pattern by returning a new state object that has the old state spread into it and the appropriate properties merged over the top.
So, that's our first reducer complete. In the next section, we'll create our store.
In this section, we'll create a store that is going to hold our state and manage the actions and reducer:
import { applyMiddleware, combineReducers, createStore, Store } from "redux";
import thunk from "redux-thunk";
import { productsReducer } from "./ProductsReducer";
import { IProductsState } from "./ProductsTypes";
export interface IApplicationState {
products: IProductsState;
}
At this point, the interface simply contains our products state.
const rootReducer = combineReducers<IApplicationState>({
products: productsReducer
});
export default function configureStore(): Store<IApplicationState> {
const store = createStore(rootReducer, undefined, applyMiddleware(thunk));
return store;
}
We've made a great start on our store. In the next section, we'll start to connect our React shop to our store.
In this section, we'll connect the Products page to our store. The first job is to add the React Redux Provider component which we'll do in the next section.
The Provider component can pass the store to components beneath it at any level. So, in this section we are going to add Provider right at the top of our component hierarchy so that all our components can access it:
import { Provider} from "react-redux";
import { Store } from "redux";
import configureStore from "./Store";
import { IApplicationState } from "./Store";
interface IProps {
store: Store<IApplicationState>;
}
const Root: React.SFC<IProps> = props => {
return ();
};
This Root component is going to be our new root element. It takes our store in as a prop.
const Root: React.SFC<IProps> = props => {
return (
<Routes />
);
};
return (
<Provider store={props.store}>
<Routes />
</Provider>
);
We've placed Provider at the top of the component tree with our store passed into it.
const store = configureStore();
ReactDOM.render(<Root store={store} />, document.getElementById(
"root"
) as HTMLElement);
We first create the store using our configureStore function and then pass this into our Root component.
So, this is the first step in connecting our components to the store. In the next section, we'll complete this connection for our ProductPage component.
We are getting close to seeing our enhanced shop in action. In this section, we will connect our store to several components.
The first component we are going to connect to the store is going to be the ProductsPage component.
Let's open up ProductsPage.tsx and start to refactor it:
import { connect } from "react-redux";
We'll use the connect function at the end of this section to connect the ProductsPage component to the store.
import { IApplicationState } from "./Store";
import { getProducts } from "./ProductsActions";
class ProductsPage extends React.Component<RouteComponentProps> {
public async componentDidMount() { ... }
public render() { ... }
}
interface IProps extends RouteComponentProps {
getProducts: typeof getProducts;
loading: boolean;
products: IProduct[];
}
class ProductsPage extends React.Component<IProps> { ... }
So, we'll get the following data passed from the store to our component:
public componentDidMount() {
this.props.getProducts();
}
import { IProduct } from "./ProductsData";
public render() {
const searchParams = new URLSearchParams(this.props.location.search);
const search = searchParams.get("search") || "";
return ( ... );
}
<ul className="product-list">
{this.props.products.map(product => {
if (!search || (search && product.name.toLowerCase().indexOf(search.toLowerCase()) > -1)
) { ... }
})}
</ul>
const mapStateToProps = (store: IApplicationState) => {
return {
loading: store.products.productsLoading,
products: store.products.products
};
};
So, we are getting whether products are being loaded as well as the products from the store and passing these to our props.
const mapDispatchToProps = (dispatch: any) => {
return {
getProducts: () => dispatch(getProducts())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductsPage);
The connect HOC connects the component to our store, which is provided to us by the Provider component higher up in the component tree. The connect HOC also invokes the mapper functions that map the state and action creators from the store into the component props.
npm start
We should find the page behaves exactly the same as it did before. The only difference is now the state is being managed in our Redux store.
In the next section, we are going enhance our Products page by adding the loading spinner we already have in our project.
In this section, we are going to add a loading spinner to the Products page. Before we can do this, we are going to extract the list of products out into its own component. We can then add the withLoader HOC to the extracted component:
import * as React from "react";
import { Link } from "react-router-dom";
import { IProduct } from "./ProductsData";
import withLoader from "./withLoader";
interface IProps {
products?: IProduct[];
search: string;
}
const ProductsList: React.SFC<IProps> = props => {
const search = props.search;
return ();
};
return (
<ul className="product-list">
{props.products &&
props.products.map(product => {
if (
!search ||
(search &&
product.name.toLowerCase().indexOf(search.toLowerCase())
> -1)
) {
return (
<li key={product.id} className="product-list-item">
<Link to={`/products/${product.id}`}>{product.name}
</Link>
</li>
);
} else {
return null;
}
})}
</ul>
);
Note that we remove references to this after moving the JSX.
export default withLoader(ProductsList);
return (
<div className="page-container">
<p>
Welcome to React Shop where you can get all your tools for ReactJS!
</p>
<ProductsList
search={search}
products={this.props.products}
loading={this.props.loading}
/>
</div>
);
import ProductsList from "./ProductsList";
If we go to the running app and browse to the Products page, we should now see a loading spinner while the products load:

So, our Products page is nicely wired up to our Redux store now. In the next section, we'll wire up the Product page to the store.
Connecting the ProductPage component to our store is first going to require a little work in our store. We need additional state for the current product, as well as whether it has been added to the basket. We also need additional actions and reducer code to get a product and add it to the basket:
export interface IProductsState {
readonly currentProduct: IProduct | null;
...
}
export enum ProductsActionTypes {
GETALL = "PRODUCTS/GETALL",
GETSINGLE = "PRODUCTS/GETSINGLE",
LOADING = "PRODUCTS/LOADING"
}
export interface IProductsGetSingleAction {
type: ProductsActionTypes.GETSINGLE;
product: IProduct;
}
export type ProductsActions = IProductsGetAllAction | IProductsGetSingleAction | IProductsLoadingAction;
import { getProduct as getProductFromAPI, getProducts as getProductsFromAPI} from "./ProductsData";
import { IProductsGetAllAction, IProductsGetSingleAction, IProductsLoadingAction, IProductsState, ProductsActionTypes } from "./productsTypes";
export const getProduct: ActionCreator<ThunkAction<Promise<any>, IProductsState, null, IProductsGetSingleAction>> = (id: number) => {
return async (dispatch: Dispatch) => {
dispatch(loading());
const product = await getProductFromAPI(id);
dispatch({
product,
type: ProductsActionTypes.GETSINGLE
});
};
};
This is very similar to the getProducts action creator. The only difference in structure is that the action creator takes in a parameter for the product ID.
const initialProductState: IProductsState = {
currentProduct: null,
...
};
switch (action.type) {
...
case ProductsActionTypes.GETSINGLE: {
return {
...state,
currentProduct: action.product,
productsLoading: false
};
}
}
We spread the old state into a new object, overwrite the current project, and set the loading state to false.
So, that's some of the state management that the Product page needs in the Redux store. However, we aren't managing the basket yet in our store. We'll do this in the next section.
We'll add state management for our basket in this section. We'll create a new section in our store for this.
import { IProduct } from "./ProductsData";
export enum BasketActionTypes {
ADD = "BASKET/ADD"
}
export interface IBasketState {
readonly products: IProduct[];
}
export interface IBasketAdd {
type: BasketActionTypes.ADD;
product: IProduct;
}
export type BasketActions = IBasketAdd;
import { BasketActionTypes, IBasketAdd } from "./BasketTypes";
import { IProduct } from "./ProductsData";
export const addToBasket = (product: IProduct): IBasketAdd => ({
product,
type: BasketActionTypes.ADD
});
This is the action creator for adding to the basket. The function takes in a product and returns it in the action with the appropriate action type.
import { Reducer } from "redux";
import { BasketActions, BasketActionTypes, IBasketState } from "./BasketTypes";
const initialBasketState: IBasketState = {
products: []
};
export const basketReducer: Reducer<IBasketState, BasketActions> = (state = initialBasketState, action) => {
switch (action.type) {
case BasketActionTypes.ADD: {
return {
...state,
products: state.products.concat(action.product)
};
}
}
return state || initialBasketState;
};
This follows the same pattern as productsReducer.
One interesting point to note is how we elegantly add the product to the products array without mutating the original array. We use the JavaScript concat function, which creates a new array by merging the original with the parameter passed in. This is a great function to use in reducers where state changes involve adding items to arrays.
import { basketReducer } from "./BasketReducer";
import { IBasketState } from "./BasketTypes";
export interface IApplicationState {
basket: IBasketState;
products: IProductsState;
}
export const rootReducer = combineReducers<IApplicationState>({
basket: basketReducer,
products: productsReducer
});
Now that we've adjusted our store, we can connect our ProductPage component to it.
In this section, we'll connect the ProductPage component to our store:
import { connect } from "react-redux";
import { addToBasket } from "./BasketActions";
import { getProduct } from "./ProductsActions";
import { IApplicationState } from "./Store";
import { IProduct } from "./ProductsData";
interface IProps extends RouteComponentProps<{ id: string }> {
addToBasket: typeof addToBasket;
getProduct: typeof getProduct;
loading: boolean;
product?: IProduct;
added: boolean;
}
class ProductPage extends React.Component<IProps> { ... }
So, the IState interface and the Props type should be removed after this movement.
public componentDidMount() {
if (this.props.match.params.id) {
const id: number = parseInt(this.props.match.params.id, 10);
this.props.getProduct(id);
}
}
Notice that we also remove the async keyword because the method is no longer asynchronous.
public render() {
const product = this.props.product;
return (
<div className="page-container">
<Prompt when={!this.props.added} message={this.navAwayMessage}
/>
{product || this.props.loading ? (
<Product
loading={this.props.loading}
product={product}
inBasket={this.props.added}
onAddToBasket={this.handleAddClick}
/>
) : (
<p>Product not found!</p>
)}
</div>
);
}
private handleAddClick = () => {
if (this.props.product) {
this.props.addToBasket(this.props.product);
}
};
const mapDispatchToProps = (dispatch: any) => {
return {
addToBasket: (product: IProduct) => dispatch(addToBasket(product)),
getProduct: (id: number) => dispatch(getProduct(id))
};
};
const mapStateToProps = (store: IApplicationState) => {
return {
basketProducts: store.basket.products,
loading: store.products.productsLoading,
product: store.products.currentProduct || undefined
};
};
Note that we map a null currentProduct to undefined.
const mapStateToProps = (store: IApplicationState) => {
return {
added: store.basket.products.some(p => store.products.currentProduct ? p.id === store.products.currentProduct.id : false),
...
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductPage);
We can now go to the running app, visit the product page, and add it to the basket. The Add to basket button should disappear after it is clicked. If we browse to a different product and then come back to a product we've already added to the basket, the Add to basket button shouldn't be present.
So, we now have both the Products and Product pages connected to our Redux store. In the next section, we'll create a basket summary component and connect that to the store.
In this section, we'll create a new component called BasketSummary. This will show the number of items in the basket and will be located in the top right of our shop. The following screenshot shows what the basket summary will look like in the top right of the screen:

import * as React from "react";
interface IProps {
count: number;
}
const BasketSummary: React.SFC<IProps> = props => {
return <div className="basket-summary">{props.count}</div>;
};
export default BasketSummary;
This is a simple component that takes in the number of products in the basket as a prop and displays this value in a div styled with a basket-summary CSS class.
.basket-summary {
display: inline-block;
margin-left: 10px;
padding: 5px 10px;
border: white solid 2px;
}
import BasketSummary from "./BasketSummary";
import { connect } from "react-redux";
import { IApplicationState } from "./Store";
interface IProps extends RouteComponentProps {
basketCount: number;
}
class Header extends React.Component<IProps, IState> {
public constructor(props: IProps) { ... }
...
}
We're going to keep the search state local in this component.
<header className="header">
<div className="search-container">
<input ... />
<BasketSummary count={this.props.basketCount} />
</div>
...
</header>
const mapStateToProps = (store: IApplicationState) => {
return {
basketCount: store.basket.products.length
};
};
export default connect(mapStateToProps)(withRouter(Header));
Now that the Header component is consuming the BasketSummary component and is also connected to the store, we should be able to add products to the basket in the running app and see the basket summary increase.
So, that completes this section on connecting components to the store. We have connected a few different components to the store, so hopefully this process is making good sense now.
In the next section, we'll explore a Redux-like approach for managing state within a component.
Redux is great for managing complex state across our app. It is a little heavy though if the state we are managing only exists within a single component. Obviously, we can manage these cases with setState (for class components) or useState (for function components). However, what if the state is complex? There may be lots of pieces of state and the state interactions may involve lots of steps with some of them being asynchronous. In this section, we'll explore an approach for managing these cases with the useReducer function in React. Our example will be contrived and simple but it will give us an understanding of this approach.
We are going to add a Like button to the Product page in our React shop. Users will be able to like a product several times. The Product component will keep track of the number of likes and the date and time of the last like in its state:

interface ILikeState {
likes: number;
lastLike: Date | null;
}
const initialLikeState: ILikeState = {
likes: 0,
lastLike: null
};
enum LikeActionTypes {
LIKE = "LIKE"
}
interface ILikeAction {
type: LikeActionTypes.LIKE;
now: Date;
}
type LikeActions = ILikeAction;
const [state, dispatch]: [
ILikeState,
(action: ILikeAction) => void
] = React.useReducer(reducer, initialLikeState);
Let's break this down:
const [{ likes, lastLike }, dispatch]: [
ILikeState,
(action: ILikeAction) => void
] = React.useReducer(reducer, initialLikeState);
{!props.inBasket && (
<button onClick={handleAddClick}>Add to basket</button>
)}
<div className="like-container">
{likes > 0 && (
<div>{`I like this x ${likes}, last at ${lastLike}`}</div>
)}
<button onClick={handleLikeClick}>
{likes > 0 ? "Like again" : "Like"}
</button>
</div>
.like-container {
margin-top: 20px;
}
.like-container button {
margin-top: 5px;
}
const handleLikeClick = () => {
dispatch({ type: LikeActionTypes.LIKE, now: new Date() });
};
const reducer = (state: ILikeState = initialLikeState, action: LikeActions) => {
switch (action.type) {
case LikeActionTypes.LIKE:
return { ...state, likes: state.likes + 1, lastLike: action.now };
}
return state;
};
If we try this out, we'll initially see a Like button after we navigate to the Product page. If we click it, the button text turns to Like again and a piece of text appears above it indicating how many likes there are and the last time it was liked.
This implementation feels very similar to implementing actions and reducers in a Redux store but this is all within a component. This is overkill for the example we have just been through but could prove useful where we need to manage lots more pieces of state.
We started the chapter by introducing ourselves to Redux, learning the principles and key concepts. We learned that the state is stored in a single object and changed by pure functions called reducers when actions are dispatched.
We created our own store in our React shop to put the theory into practice. Here are some key points we learned in our implementation:
We then connected some components to the store. Here are the key points in this process:
There are lots of bits and pieces to get our heads around when implementing Redux within our React apps. It does shine in scenarios where the state management is complex because Redux forces us to break the logic up into separate pieces that are easy to understand and maintain.
We learned that we can use a Redux-like approach within just a single component by leveraging React's useReducer function. This can be used when the state is complex and just exists in a single component.
One task that Redux actions often do is interact with a REST API. We are going to learn how we can interact with REST APIs in both class- and function-based components in the next chapter. We'll also learn about a native function we use to call to a REST API as well as a popular open source library.
Before we end this chapter, let's test our knowledge with some questions:
export const basketReducer: Reducer<IBasketState, BasketActions> = (
state = initialBasketState,
action
) => {
switch (action.type) {
case BasketActionTypes.ADD: {
state.products.push(action.product);
}
}
return state || initialBasketState;
};
The following links are good resources of further information on React Redux:
Interacting with RESTful APIs is a very common task we need to do when building an app, and it always results in us having to write asynchronous code. So, to begin with in this chapter, we'll have a detailed look at asynchronous code in general.
There are many libraries that we can use to help us interact with REST APIs. In this chapter, we'll look at both a native browser function and a popular open source library for interacting with REST APIs. We'll discover the additional features that the open source library has over the native function. We will also look at how we can interact with a REST API in both React class and function-based components.
In this chapter, we'll learn the following topics:
We use the following technologies in this chapter:
npm install -g typescript
TypeScript code is executed synchronously by default, where each line of code is executed after each other. However, TypeScript code can also be asynchronous, which means things can happen independently of our code. Calling a REST API is an example of asynchronous code because the API request is handled outside of our TypeScript code. So, interacting with a REST API forces us to write asynchronous code.
In this section, we'll take the time to understand the approaches we can take when writing asynchronous code before using them to interact with RESTful APIs. We'll start in the next section by looking at callbacks.
A callback is a function we pass as a parameter to an asynchronous function to call when the asynchronous function is complete. In the next section, we'll go through an example of writing asynchronous code with a callback.
Let's go through an example of using callbacks in asynchronous code in the TypeScript playground. Let's enter the following code:
let firstName: string;
setTimeout(() => {
firstName = "Fred";
console.log("firstName in callback", firstName);
}, 1000);
console.log("firstName after setTimeout", firstName);
The code calls the JavaScript setTimeout function, which is asynchronous. It takes in a callback as the first parameter and the number of milliseconds the execution should wait until the callback is executed as the second parameter.
We use an arrow function as the callback function, where we set the firstName variable to "Fred" and output this to the console. We also log firstName in the console immediately after the call to setTimeout.
So, which console.log statement will get executed first? If we run the code and look at the console, we'll see that the last line is executed first:

The key point is that after setTimeout is called, execution carries on to the next line of code. Execution doesn't wait for the callback to be called. This can make code that includes callbacks harder to read than synchronous code, particularly when we have callbacks nested within callbacks. This is referred to as callback hell by many developers!
So, how do we handle errors in asynchronous callback code? We'll find out in the next section.
In this section, we are going to explore how we can handle errors when using callback code:
try {
setTimeout(() => {
throw new Error("Something went wrong");
}, 1000);
} catch (ex) {
console.log("An error has occurred", ex);
}
We are again using setTimeout to experiment with callbacks. This time, we throw an error inside the callback. We are hoping to catch the error outside the callback using a try / catch around the setTimeout function.
If we run the code, we see that we don't catch the error:

interface IResult {
success: boolean;
error?: any;
}
let result: IResult = { success: true };
setTimeout(() => {
try {
throw new Error("Something went wrong");
} catch (ex) {
result.success = false;
result.error = ex;
}
}, 1000);
console.log(result);
This time, the try / catch is within the callback. We use a variable, result, to determine whether the callback was executed successfully, along with any error. The IResult interface gives us a nice bit of type safety with the result variable.
If we run this code, we'll see that we successfully handle the error:

So, handling errors along with reading callback-based code is a challenge. Luckily, there are alternative approaches that deal with these challenges, which we'll go through in the next sections.
A promise is a JavaScript object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. We'll have a look at an example of consuming a promised-based function in the next section, followed by creating our own promised-based function after that.
Let's have a quick look at some code that exposes a promise-based API:
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(data => console.log(data))
.catch(json => console.log("error", json));
The code execution flows down as we would read it. We also don't have to do any additional work in the then methods to handle errors. So, this is much nicer than working with callback-based asynchronous code.
In the next section we'll create our own promised based function.
In this section, we'll create a wait function to asynchronously wait a number of milliseconds that passed in as a parameter:
const wait = (ms: number) => {
return new Promise((resolve, reject) => {
if (ms > 1000) {
reject("Too long");
}
setTimeout(() => {
resolve("Sucessfully waited");
}, ms);
});
};
wait(500)
.then(result => console.log("then >", result))
.catch(error => console.log("catch >", error));
The function simply outputs the result or error to the console after waiting 500 milliseconds.
So, let's give this a try and run it:

As we can see, the output in the console indicates that the then method is executed.
wait(1500)
.then(result => console.log("then >", result))
.catch(error => console.log("catch >", error));
As expected, the catch method is executed:

So, promises give us a nice way of writing asynchronous code. However, there's another approach that we have used a number of times earlier in this book. We'll go through this method in the next section.
async and await are two JavaScript keywords we can use to make asynchronous code read almost identically to synchronous code:
const someWork = async () => {
try {
const result = await wait(500);
console.log(result);
} catch (ex) {
console.log(ex);
}
};
someWork();
So, the code is very similar to how you would write it in a synchronous manner.
If we run this example, we get confirmation that the console.log statement in the try branch waited until the wait function had completely finished before executing:

const result = await wait(1500);
If we run this, we see that an error is raised and caught:

So, async and await make our code nice and easy to read. A bonus for using these in TypeScript is that the code can be transpiled to work in older browsers. So, for example, we can code with async and await and still support IE.
Now that we have a good understanding of writing asynchronous code, we'll put this into practice when we interact with RESTful APIs in the following sections.
The fetch function is a native JavaScript function that we can use to interact with RESTful APIs. In this section, we'll go through some common RESTful API interactions using fetch, starting with getting data. Throughout this section, we are going to interact with the fantastic JSONPlaceholder REST API.
In this section, we'll use fetch to get some posts from the JSONPlaceholder REST API, starting with a basic GET request.
Let's open up the TypeScript playground and enter the following:
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(data => console.log(data));
Here are some key points:
If we run the code, we should see an array of posts output to the console:

Very often, we need to check the status of the request. We can do this as follows:
fetch("https://jsonplaceholder.typicode.com/posts").then(response => {
console.log(response.status, response.ok);
});
If we run the previous code, we get 200 and true output to the console.
Let's try an example request where the post doesn't exist:
fetch("https://jsonplaceholder.typicode.com/posts/1001").then(response => {
console.log(response.status, response.ok);
});
If we run the preceding code, we get 404 and false output to the console.
As we would expect with a promised-based function, we handle errors in the catch method:
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(data => console.log(data))
.catch(json => console.log("error", json));
However, the catch method doesn't catch responses that aren't in the 200 range. An example of this was in the previous example, where we got 404 in the response status code. So, an HTTP error status code can be handled in the first then method and not the catch method.
So, what is the catch method for? The answer is to catch network errors.
So, that's how to get data using fetch. In the next section, we'll cover posting data.
In this section, we'll use fetch to create some data with the JSONPlaceholder REST API.
Creating data via a REST API usually involves using the HTTP POST method with the data we want to create in the request body.
Let's open up the TypeScript playground and enter the following:
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
body: JSON.stringify({
title: "Interesting post",
body: "This is an interesting post about ...",
userId: 1
})
})
.then(response => {
console.log(response.status);
return response.json();
})
.then(data => console.log(data));
The fetch call is largely the same as for getting data. The key difference is the second parameter, which is an options object that can contain the method and body for the request. Notice also that the body needs to be a string.
If we run the preceding code, we get a 201 and an object containing the generated post ID in the console.
Very often, we need to include HTTP headers in the request. We can specify these in the options object in a headers property:
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "bearer some-bearer-token"
},
body: JSON.stringify({
title: "Interesting post",
body: "This is an interesting post about ...",
userId: 1
})
})
.then(response => {
console.log(response.status);
return response.json();
})
.then(data => console.log(data));
Request headers can be used in this way for any HTTP method and not just an HTTP POST. For example, we can use this for a GET request as follows:
fetch("https://jsonplaceholder.typicode.com/posts/1", {
headers: {
"Content-Type": "application/json",
Authorization: "bearer some-bearer-token"
}
}).then(...);
So, that's how to use fetch to post data to a REST API. In the next section, we'll look at changing data.
In this section, we'll use fetch to change some data via a REST API.
A common way to change data is via a PUT request. Let's open up the TypeScript playground and enter the following:
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "Corrected post",
body: "This is corrected post about ...",
userId: 1
})
})
.then(response => {
console.log(response.status);
return response.json();
})
.then(data => console.log(data));
So, the structure of a fetch call to do an HTTP PUT is very similar to a POST request. The only difference is that we specify the method property in the options object as PUT.
If we run the preceding code, we get 200 and the updated POST object output to the console.
Some REST APIs offer PATCH requests, which allow us to submit changes to a portion of a resource. Let's open up the TypeScript playground and enter the following:
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "PATCH",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({
title: "Corrected post"
})
})
.then(response => {
console.log(response.status);
return response.json();
})
.then(data => console.log(data));
So, we are submitting a change to the title of the post with the PATCH HTTP method. If we run the preceding code, we get 200 and the updated post object output to the console.
So, that's how to PUT and PATCH using fetch. In the next section, we'll delete some data.
Generally, we delete data via a DELETE HTTP method on a REST API. Let's enter the following in the TypeScript playground:
fetch("https://jsonplaceholder.typicode.com/posts/1", {
method: "DELETE"
}).then(response => {
console.log(response.status);
});
So, we are requesting to delete a post with the DELETE method.
If we run the preceding code, we get 200 output to the console.
So, we've learned how to interact with a RESTful API with the native fetch function. In the next section, we'll look at doing the same with a popular open source library and understanding its benefits over fetch.
axios is a popular open source JavaScript HTTP client. We're going to build a little React app that creates, reads, updates, and deletes posts from the JSONPlaceholder REST API. Along the way, we'll discover some of the benefits of axios over fetch. Our first job in the next section is to install axios.
Before we install axios, we are going to quickly create our little React app:
npx create-react-app crud-api --typescript
Note that the version of React we use needs to be at least version 16.7.0-alpha.0. We can check this in the package.json file. If the version of React in package.json is older than 16.7.0-alpha.0, then we can install this version using the following command:
npm install react@16.7.0-alpha.0
npm install react-dom@16.7.0-alpha.0
cd crud-api
npm install tslint tslint-react tslint-config-prettier --save-dev
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
"rules": {
"ordered-imports": false,
"object-literal-sort-keys": false,
"jsx-no-lambda": false,
"no-debugger": false,
"no-console": false,
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
class App extends Component {
public render() {
return ( ... );
}
}
npm install axios
Note that axios has TypeScript types within it, so, we don't need to install them.
npm start
The app will then start up and run in our browser. In the next section, we'll use axios to get posts from JSONPlaceholder.
In this section, we are going to render posts from JSONPlaceholder in the App component.
We'll start off by getting the posts using a basic GET request with axios, and then rendering them in an unordered list:
import axios from "axios";
interface IPost {
userId: number;
id?: number;
title: string;
body: string;
}
interface IState {
posts: IPost[];
}
class App extends React.Component<{}, IState> { ... }
class App extends React.Component<{}, IState> {
public constructor(props: {}) {
super(props);
this.state = {
posts: []
};
}
}
public componentDidMount() {
axios
.get<IPost[]>("https://jsonplaceholder.typicode.com/posts")
.then(response => {
this.setState({ posts: response.data });
});
}
So, straight away this is nicer than fetch in two ways:
public render() {
return (
<div className="App">
<ul className="posts">
{this.state.posts.map(post => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</li>
))}
</ul>
</div>
);
}
We use the posts array's map function to display the posts in an unordered list.
.posts {
list-style: none;
margin: 0px auto;
width: 800px;
text-align: left;
}
If we look at the running app, it will now look like the following:

So, a basic GET request with axios is nice and easy. We need to use the componentDidMount life cycle method in a class component to make a REST API call that will have data from the response rendered.
How do we handle errors though? We'll cover this in the next section.
.get<IPost[]>("https://jsonplaceholder.typicode.com/postsX")
If we look at the running app, the posts are no longer being rendered.
axios
.get<IPost[]>("https://jsonplaceholder.typicode.com/postsX")
.then( ... )
.catch(ex => {
const error =
ex.response.status === 404
? "Resource not found"
: "An unexpected error has occurred";
this.setState({ error });
});
So, unlike fetch, HTTP status error codes can be handled in the catch method. The error object argument in catch contains a response property containing information about the response, including the HTTP status code.
interface IState {
posts: IPost[];
error: string;
}
class App extends React.Component<{}, IState> {
public constructor(props: {}) {
super(props);
this.state = {
posts: [],
error: ""
};
}
}
<ul className="posts">
...
</ul>
{this.state.error && <p className="error">{this.state.error}</p>}
.error {
color: red;
}
If we look at the running app now, we'll see Resource not found in red.
.get<IPost[]>("https://jsonplaceholder.typicode.com/posts")
So, handling HTTP errors with axios is different than with fetch. We handle them in the first then method with fetch, whereas we handle them in the catch method with axios.
In order to include HTTP headers in the request, we need to add a second parameter to the get function, which can contain various options, including HTTP headers.
Let's add an HTTP header for the content type in our request:
.get<IPost[]>("https://jsonplaceholder.typicode.com/posts", {
headers: {
"Content-Type": "application/json"
}
})
So, we define the HTTP headers in an object in a property called headers.
If we look at the running app, it will be exactly the same. The JSONPlaceholder REST API doesn't require the content type, but other REST APIs that we interact with may do.
In the next section, we'll look at something that is not easily achieved in the fetch function, which is the ability to specify a timeout on the request.
Timing out requests after a certain amount of time can improve the user experience in our app:
.get<IPost[]>("https://jsonplaceholder.typicode.com/posts", {
headers: {
"Content-Type": "application/json"
},
timeout: 1
})
So, adding a timeout to an axios request is super simple. We just add a timeout property to the options object with an appropriate number of milliseconds. We have specified just 1 millisecond, so that we can hopefully see the request timing out.
.catch(ex => {
const error =
ex.code === "ECONNABORTED"
? "A timeout has occurred"
: ex.response.status === 404
? "Resource not found"
: "An unexpected error has occurred";
this.setState({ error });
});
So, we check the code property in the caught error object in order to determine whether a timeout has occurred.
If we look at the running app, we should get confirmation that a timeout has occurred with A timeout has occurred displayed in red.
.get<IPost[]>("https://jsonplaceholder.typicode.com/posts", {
...
timeout: 5000
})
Allowing the user to cancel a request can improve the user experience in our app. We'll do this with the help of axios in this section:
import axios, { CancelTokenSource } from "axios";
interface IState {
posts: IPost[];
error: string;
cancelTokenSource?: CancelTokenSource;
loading: boolean;
}
this.state = {
posts: [],
error: "",
loading: true
};
We've defined the cancel token as optional so we don't need to initialize it in the constructor.
public componentDidMount() {
const cancelToken = axios.CancelToken;
const cancelTokenSource = cancelToken.source();
this.setState({ cancelTokenSource });
axios
.get<IPost[]>(...)
.then(...)
.catch(...);
}
.get<IPost[]>("https://jsonplaceholder.typicode.com/posts", {
cancelToken: cancelTokenSource.token,
...
})
.catch(ex => {
const error = axios.isCancel(ex)
? "Request cancelled"
: ex.code === "ECONNABORTED"
? "A timeout has occurred"
: ex.response.status === 404
? "Resource not found"
: "An unexpected error has occurred";
this.setState({ error, loading: false });
});
So, we use the isCancel function in axios to check if the request has been canceled.
.then(response => {
this.setState({ posts: response.data, loading: false });
})
{this.state.loading && (
<button onClick={this.handleCancelClick}>Cancel</button>
)}
<ul className="posts">...</ul>
private handleCancelClick = () => {
if (this.state.cancelTokenSource) {
this.state.cancelTokenSource.cancel("User cancelled operation");
}
};
In order to cancel the request, the cancel method is called on the cancel token source.
So, users can now cancel requests by clicking the Cancel button.
axios
.get<IPost[]>( ... )
.then(response => { ... })
.catch(ex => { ... });
cancelTokenSource.cancel("User cancelled operation");
If we look at the running app, we should see verification that the request was cancelled by Request cancelled being displayed in red.
So, axios makes it really easy to improve our app's user experience by adding the ability to cancel requests.
Before we move on to the next section, wherein we look at using axios to create data, let's remove the line we just added to cancel the request immediately after it was made.
Let's move on to creating data now. We are going to allow the user to enter a post title and body and save it:
interface IState {
...
editPost: IPost;
}
public constructor(props: {}) {
super(props);
this.state = {
...,
editPost: {
body: "",
title: "",
userId: 1
}
};
}
<div className="App">
<div className="post-edit">
<input
type="text"
placeholder="Enter title"
value={this.state.editPost.title}
onChange={this.handleTitleChange}
/>
<textarea
placeholder="Enter body"
value={this.state.editPost.body}
onChange={this.handleBodyChange}
/>
<button onClick={this.handleSaveClick}>Save</button>
</div>
{this.state.loading && (
<button onClick={this.handleCancelClick}>Cancel</button>
)}
...
</div>
private handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
editPost: { ...this.state.editPost, title: e.currentTarget.value }
});
};
private handleBodyChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
this.setState({
editPost: { ...this.state.editPost, body: e.currentTarget.value }
});
};
.post-edit {
display: flex;
flex-direction: column;
width: 300px;
margin: 0px auto;
}
.post-edit input {
font-family: inherit;
width: 100%;
margin-bottom: 5px;
}
.post-edit textarea {
font-family: inherit;
width: 100%;
margin-bottom: 5px;
}
.post-edit button {
font-family: inherit;
width: 100px;
}
private handleSaveClick = () => {
axios
.post<IPost>(
"https://jsonplaceholder.typicode.com/posts",
{
body: this.state.editPost.body,
title: this.state.editPost.title,
userId: this.state.editPost.userId
},
{
headers: {
"Content-Type": "application/json"
}
}
)
};
.then(response => {
this.setState({
posts: this.state.posts.concat(response.data)
});
});
So, we concatenate the new post with the existing post to create a new posts array for the state.
The structure of the post function call is very similar to get. In fact, we could add error handling, a timeout, and the ability to cancel the request in the same way as we did for get.
If we add a new post in the running app and click the Save button, we see it added to the bottom of the posts list.
Next up, we will allow users to update posts.
Let's move on to updating data now. We are going to allow the user to click an Update button in an existing post to change and save it:
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<button onClick={() => this.handleUpdateClick(post)}>
Update
</button>
</li>
private handleUpdateClick = (post: IPost) => {
this.setState({
editPost: post
});
};
private handleSaveClick = () => {
if (this.state.editPost.id) {
// TODO - make a PUT request
} else {
axios
.post<IPost>( ... )
.then( ... );
}
};
if (this.state.editPost.id) {
axios
.put<IPost>(
`https://jsonplaceholder.typicode.com/posts/${
this.state.editPost.id
}`,
this.state.editPost,
{
headers: {
"Content-Type": "application/json"
}
}
)
.then(() => {
this.setState({
editPost: {
body: "",
title: "",
userId: 1
},
posts: this.state.posts
.filter(post => post.id !== this.state.editPost.id)
.concat(this.state.editPost)
});
});
} else {
...
}
So, we filter out and concatenate the updated post to create a new posts array for the state.
The structure of the put function call is very similar to get and post. Again, we could add error handling, a timeout, and the ability to cancel the request in the same way as we did for get.
In the running app, if we click an Update button in a post, change the title and body, and click the Save button, we see it removed from where it was and added to the bottom of the posts list with the new title and body.
If we want to PATCH a post, we can use the patch axios method. This has the same structure as put but instead of passing the whole object that is being changed, we can just pass the values that need updating.
In the next section, we will allow users to delete posts.
Let's move on to deleting data now. We are going to allow the user to click a Delete button in an existing post to delete it:
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<button onClick={() => this.handleUpdateClick(post)}>
Update
</button>
<button onClick={() => this.handleDeleteClick(post)}>
Delete
</button>
</li>
private handleDeleteClick = (post: IPost) => {
axios
.delete(`https://jsonplaceholder.typicode.com/posts/${post.id}`)
.then(() => {
this.setState({
posts: this.state.posts.filter(p => p.id !== post.id)
});
});
};
So, we use the axios delete method to make an HTTP DELETE request, which follows the same structure as the other methods.
If we go to the running app, we should see a delete button in each post. If we click one of the buttons, we'll see it removed from the list after a short delay.
So, that concludes this section on axios with class components. We've seen that the axios functions are a little cleaner than fetch, and features such as the ability to have typed responses, timeouts, and request cancellation make it a popular choice for many developers. In the next section, we'll refactor the App component we have just implemented to be a function component.
In this section, we'll implement REST API calls using axios in a function component. We'll refactor the App component we built in the last section:
const defaultPosts: IPost[] = [];
const App: React.SFC = () => {}
const App: React.SFC = () => {
const [posts, setPosts]: [IPost[], (posts: IPost[]) => void] = React.useState(defaultPosts);
const [error, setError]: [string, (error: string) => void] = React.useState("");
const cancelToken = axios.CancelToken;
const [cancelTokenSource, setCancelTokenSource]: [CancelTokenSource,(cancelSourceToken: CancelTokenSource) => void] = React.useState(cancelToken.source());
const [loading, setLoading]: [boolean, (loading: boolean) => void] = React.useState(false);
const [editPost, setEditPost]: [IPost, (post: IPost) => void] = React.useState({
body: "",
title: "",
userId: 1
});
}
So, we use the useState function to define and initialize all these pieces of state.
React.useEffect(() => {
// TODO - get posts
}, []);
React.useEffect(() => {
axios
.get<IPost[]>("https://jsonplaceholder.typicode.com/posts", {
cancelToken: cancelTokenSource.token,
headers: {
"Content-Type": "application/json"
},
timeout: 5000
});
}, []);
React.useEffect(() => {
axios
.get<IPost[]>(...)
.then(response => {
setPosts(response.data);
setLoading(false);
});
}, []);
React.useEffect(() => {
axios
.get<IPost[]>(...)
.then(...)
.catch(ex => {
const err = axios.isCancel(ex)
? "Request cancelled"
: ex.code === "ECONNABORTED"
? "A timeout has occurred"
: ex.response.status === 404
? "Resource not found"
: "An unexpected error has occurred";
setError(err);
setLoading(false);
});
}, []);
const handleCancelClick = () => {
if (cancelTokenSource) {
cancelTokenSource.cancel("User cancelled operation");
}
};
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEditPost({ ...editPost, title: e.currentTarget.value });
};
const handleBodyChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setEditPost({ ...editPost, body: e.currentTarget.value });
};
const handleSaveClick = () => {
if (editPost.id) {
axios
.put<IPost>(
`https://jsonplaceholder.typicode.com/posts/${editPost.id}`,
editPost,
{
headers: {
"Content-Type": "application/json"
}
}
)
.then(() => {
setEditPost({
body: "",
title: "",
userId: 1
});
setPosts(
posts.filter(post => post.id !== editPost.id).concat(editPost)
);
});
} else {
axios
.post<IPost>(
"https://jsonplaceholder.typicode.com/posts",
{
body: editPost.body,
title: editPost.title,
userId: editPost.userId
},
{
headers: {
"Content-Type": "application/json"
}
}
)
.then(response => {
setPosts(posts.concat(response.data));
});
}
};
const handleUpdateClick = (post: IPost) => {
setEditPost(post);
};
const handleDeleteClick = (post: IPost) => {
axios
.delete(`https://jsonplaceholder.typicode.com/posts/${post.id}`)
.then(() => {
setPosts(posts.filter(p => p.id !== post.id));
});
};
return (
<div className="App">
<div className="post-edit">
<input
type="text"
placeholder="Enter title"
value={editPost.title}
onChange={handleTitleChange}
/>
<textarea
placeholder="Enter body"
value={editPost.body}
onChange={handleBodyChange}
/>
<button onClick={handleSaveClick}>Save</button>
</div>
{loading && <button onClick={handleCancelClick}>Cancel</button>}
<ul className="posts">
{posts.map(post => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<button onClick={() => handleUpdateClick(post)}>Update</button>
<button onClick={() => handleDeleteClick(post)}>Delete</button>
</li>
))}
</ul>
{error && <p className="error">{error}</p>}
</div>
);
That's it! Our function component that interacts with a REST API is complete. If we try this, it should behave exactly as it did before.
The main difference in terms of REST API interaction is that we use the useEffect function to make a REST API call to get data that needs to be rendered. We still do this when the component has been mounted, like we do in class-based components. It's just a different way of tapping into that component life cycle event.
Callback-based asynchronous code can be difficult to read and maintain. Who's spent hours trying to track down the root cause of a bug in callback-based asynchronous code? Or just spent hours trying to understand what a piece of callback-based asynchronous code is trying to do? Thankfully, we now have alternative ways of writing asynchronous code.
Promise-based functions are a great improvement over callback-based asynchronous code because the code is a lot more readable and errors can be handled more easily. The async and await keywords arguably make reading asynchronous code even easier than promised-based function code because it is very close to what the synchronous equivalent would look like.
Modern browsers have a nice function called fetch for interacting with REST APIs. This is a promised-based function allowing us to easily make a request and nicely manage the response.
axios is a popular alternative to fetch. The API is arguably cleaner and allows us to better handle HTTP error codes. Timeouts and canceling requests are also made very simple using axios. axios is also TypeScript-friendly, having types baked into the library. Having played with both axios and fetch, which is your favorite?
We can interact with REST APIs in both class- and function-based components. When calling a REST API to get data to display in a first component render, we need to wait until just after the component has been mounted. In class components, we do this using the componentDidMount life cycle method. In function components, we do this using the useEffect function, passing an empty array as the second parameter. Having experienced interacting with REST APIs in both types of components, which component type are you going to use on your next React and TypeScript project?
REST APIs aren't the only type of API we are likely going to need to interact with. GraphQL is a popular alternative API server. We'll learn how we can interact with GraphQL servers in the next chapter.
Let's answer the following questions to help our knowledge of what we have just learned stick:
try {
setInterval(() => {
throw new Error("Oops");
}, 1000);
} catch (ex) {
console.log("Sorry, there is a problem", ex);
}
fetch("https://jsonplaceholder.typicode.com/posts/9999")
.then(response => {
console.log("HTTP status code", response.status);
return response.json();
})
.then(data => console.log("Response body", data))
.catch (error => console.log("Error", error));
axios
.get("https://jsonplaceholder.typicode.com/posts/9999")
.then(response => {
console.log("HTTP status code", response.status);
})
.catch(error => {
console.log("Error", error.response.status);
});
axios.get("https://jsonplaceholder.typicode.com/posts/1")
axios.put("https://jsonplaceholder.typicode.com/posts/1", {
title: "corrected title",
body: "some stuff"
});
React.useEffect(() => {
axios
.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(...)
.catch(...);
});
What is wrong with the preceding code?
The following links are good resources for further information on the topics we have covered in this chapter:
GraphQL is an open source web API language for reading and writing data that is maintained by Facebook. It allows the client to specify exactly what data is returned and request multiple data areas in a single request. This efficiency and flexibility makes it a compelling alternative to a REST API. GraphQL also supports both reading and writing data.
In this chapter, we'll start by experimenting with some GraphQL queries against GitHub to get familiar with the syntax by using the GitHub GraphQL API explorer. We'll explore how we both read and write GraphQL data and how to specify exactly the way we want the data in the response returned to us.
We'll then consume the GitHub GraphQL server in a React and TypeScript application to build a little app that searches for a GitHub repository and returns some information about it. We'll use our knowledge from the last chapter on axios to interact with the GitHub GraphQL server to start off with. We'll then switch to using Apollo, which is a client library that makes interacting with GraphQL servers a breeze.
We'll cover the following topics in this chapter:
We use the following technologies in this chapter:
In this section, we'll use the GitHub GraphQL API explorer to start to get familiar with the syntax for interacting with a GraphQL server, starting with reading data in the next section.
In order to read GraphQL data, we make what is called a query. In this section, we'll start by covering the basic GraphQL syntax and move on to how to include nested objects in a query result, and then how we can create reusable queries by allowing parameters to be passed into them.
In this section, we'll use the GitHub GraphQL API explorer to get information about our GitHub user account:
https://developer.github.com/v4/explorer/.
We will need to be signed in to our GitHub account if we aren't already.
query {
viewer {
name
}
}
This is our first GraphQL query. Here are some key points:
The query result will appear on the right-hand side:

The data we requested is returned as a JSON object. The JSON contains a data object that contains a viewer object containing the name field. The name value should be our name, since this is the name stored in our GitHub account.

If we then click on the Query link, all the objects are shown that can be queried, including viewer, which is the one we just queried. If we click into this, we see all the fields that are available within viewer.
query {
viewer {
name
avatarUrl
}
}
So, we simply add the avatarUrl field inside the viewer object with a carriage return between the name and avatarUrl fields. If we execute the query, we see avatarUrl added to the JSON result. This should be a path to an image of us.
So, we are already seeing how flexible GraphQL is with being able to specify exactly which fields we want returned in the response. In the next section, we'll take this further by specifying the nested objects we want to return.
Let's make a far more complex query in this section. We'll search for a GitHub repository, return information about it, including the number of stars it has and the last issues that have been raised as a nested array:
query {
repository (owner:"facebook", name:"react") {
name
description
}
}
This time, we are asking for the repository object, but passing two parameters for the owner and name of the repository. We are asking for the name and description of the repository to be returned.
We see that the repository and fields we asked for are returned:

query {
repository (owner:"facebook", name:"react") {
name
description
stargazers {
totalCount
}
}
}
If we execute the query, we see these results returned:

stargazers {
stars:totalCount
}
If we execute the query, we see the stars count is returned against the alias we specified:
{
"data": {
"repository": {
"name": "react",
"description": "A declarative, efficient, and flexible JavaScript library for building user interfaces.",
"stargazers": {
"stars": 114998
}
}
}
}
{
repository (owner:"facebook", name:"react") {
name
description
stargazers {
stars:totalCount
}
issues(last: 5) {
edges {
node {
id
title
url
publishedAt
}
}
}
}
}
We request the issues object by passing 5 into the last parameter. We then request an edges object containing a node object that in turn contains the issue fields we are interested in.
So, what are the edges and node objects? Why can't we just request the fields we want directly? Well, this structure is in place to facilitate cursor-based pagination.
If we execute the query, we get the last 5 issues included in our result.
So, GraphQL allows us to make a single web request for different bits of data returning just the fields we require. Doing a similar thing with the GitHub REST API would probably require multiple requests and we'd get a lot more data than we need returned to us. It is these types of queries where GraphQL shines over REST.
The query we have just made is hardcoded to get data for a specific repository. In this section, we'll define variables in the query, which essentially allow parameters to be passed into it:
query ($org: String!, $repo: String!) {
repository (owner:$org, name:$repo) {
...
}
}
{
"org": "facebook",
"repo": "react"
}

We are now getting comfortable with reading data from a GraphQL server. But how can we create new data items or update data? We'll find out in the next section.
Let's turn our attention to writing to a GraphQL server now. We do this with what are called mutations. In this section, we'll create a mutation to add a GitHub star to a repository:
query ($org: String!, $repo: String!) {
repository (owner:$org, name:$repo) {
id
...
}
}
MDEwOlJlcG9zaXRvcnkxMDI3MDI1MA==
mutation ($repoId: ID!) {
addStar(input: { starrableId: $repoId }) {
starrable {
stargazers {
totalCount
}
}
}
}
Here are some key points on this mutation:
{
"repoId": "MDEwOlJlcG9zaXRvcnkxMDI3MDI1MA=="
}

We have a good grasp on both GraphQL queries and mutations now. In the next section, we'll start to interact with a GraphQL server from a React and TypeScript app.
Interacting with a GraphQL server is done via HTTP. We learned in Chapter 9, Interacting with Restful APIs, that axios is a great HTTP client. So, in this chapter, we'll cover how to interact with a GraphQL server using axios.
To help us learn, we'll create a React and TypeScript app to return information about our GitHub account. So, our first tasks are to get a token that will give us access to query the GitHub GraphQL server and scaffold a React and TypeScript app.
The GitHub GraphQL server requires a bearer token for us to interact with it. So, let's go and generate a personal access token:
Now that we have our bearer token, let's scaffold a React and TypeScript app in the next section.
We'll follow the usual steps for scaffolding a React and TypeScript app:
npx create-react-app repo-search --typescript
Note that the version of React we use needs to be at least version 16.7.0-alpha.0. We can check this in the package.json file. If the version of React in package.json is less than 16.7.0-alpha.0, then we can install this version using the following command:
npm install react@16.7.0-alpha.0
npm install react-dom@16.7.0-alpha.0
cd repo-search
npm install tslint tslint-react tslint-config-prettier --save-dev
{
"extends": ["tslint:recommended", "tslint-react", "tslint-config-
prettier"],
"rules": {
"ordered-imports": false,
"object-literal-sort-keys": false,
"jsx-no-lambda": false,
"no-debugger": false,
"no-console": false,
},
"linterOptions": {
"exclude": [
"config/**/*.js",
"node_modules/**/*.ts",
"coverage/lcov-report/*.js"
]
}
}
class App extends Component {
public render() {
return ( ... );
}
}
npm install axios
npm start
import React from "react";
import axios from "axios";
This component will eventually contain our name and avatar from GitHub.
export const Header: React.SFC = () => {
return null;
}
import { Header } from "./Header";
<div className="App">
<header className="App-header">
<Header />
</header>
</div>
.App-header {
background-color: #282c34;
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 16px;
color: white;
}
Now that we have our React and TypeScript project in place, let's make a GraphQL query using axios:
interface IViewer {
name: string;
avatarUrl: string;
}
interface IQueryResult {
data: {
viewer: IViewer;
};
}
const [viewer, setViewer]: [
IViewer,
(viewer: IViewer) => void
] = React.useState({name: "", avatarUrl: ""});
React.useEffect(() => {
// TODO - make a GraphQL query
}, []);
We pass an empty array as the second parameter so that the query only executes when the component is mounted and not on each render.
React.useEffect(() => {
axios
.post<IQueryResult>(
"https://api.github.com/graphql",
{
query: `query {
viewer {
name
avatarUrl
}
}`
}
)
}, []);
Notice that we are doing an HTTP POST even though we are reading data. GraphQL requires us to use an HTTP POST because the details of the query are in the request body.
We are also using the interface we used earlier, IQueryResult, for the response data.
axios
.post<IQueryResult>(
"https://api.github.com/graphql",
{
query: `query {
viewer {
name
avatarUrl
}
}`
},
{
headers: {
Authorization: "bearer our-bearer-token"
}
}
)
Obviously, we need to substitute in our real bearer token that we obtained earlier from GitHub.
axios
.post<IQueryResult>(
...
)
.then(response => {
setViewer(response.data.data.viewer);
});
return (
<div>
<img src={viewer.avatarUrl} className="avatar" />
<div className="viewer">{viewer.name}</div>
<h1>GitHub Search</h1>
</div>
);
.avatar {
width: 60px;
border-radius: 50%;
}
If we look at the running app, we should see our avatar and name in our app header:

So, we've just interacted with a GraphQL server using an HTTP library. All GraphQL requests are made using the HTTP POST method, even for reading data. All GraphQL requests are made to the same endpoint as well. The resource we want data from isn't in the URL, it's in the request body. So, whilst we can use an HTTP library, like axios, for querying GraphQL servers, it feels a little strange.
In the next section, we'll look at a GraphQL client that will help us query a GraphQL server in a more natural way.
Apollo client is a client library for interacting with GraphQL servers. It has many benefits over using a generic HTTP library like axios, such as being able to read and write data declaratively with React components right in our JSX and having caching switched on right out of the box.
In this section, we'll refactor what we built in the last section with axios to use Apollo, and then extend our app a little more to include a GitHub repository search.
Our first job is to install Apollo into our project.
npm install apollo-boost react-apollo graphql
npm install @types/graphql --save-dev
{
"compilerOptions": {
"target": "es5",
"lib": ["es2015", "dom", "esnext"],
...
},
...
}
We now have everything in place to start interacting with the GitHub GraphQL server with Apollo.
Now that we have installed all the Apollo bits and pieces, let's migrate our axios code to Apollo.
We are going to start in App.tsx, where we will define our Apollo client and provide it to all the components beneath the App in the component hierarchy:
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
const client = new ApolloClient({
uri: "https://api.github.com/graphql",
headers: {
authorization: `Bearer our-bearer-token`
}
});
Obviously, we need to substitute in our real bearer token we obtained earlier from GitHub.
public render() {
return (
<ApolloProvider client={client}>
<div className="App">
<header className="App-header">
<Header />
</header>
</div>
</ApolloProvider>
);
}
Now that the ApolloClient is set up, we can start interacting with the GraphQL server.
We are now going to use the Query component to get our GitHub name and avatar, replacing the axios code:
import gql from "graphql-tag";
import { Query } from "react-apollo";
interface IQueryResult {
viewer: IViewer;
}
const GET_VIEWER = gql`
{
viewer {
name
avatarUrl
}
}
`;
So, we set the query to a GET_VIEWER variable and we have defined our query in a template literal. However, the gql function just before the template literal is a little odd. Shouldn't the template literal be in parentheses? This is actually called a tagged template literal, where the gql function from the core GraphQL library parses the template literal next to it. We end up with a query in GET-VIEWER that Apollo can use and execute.
class GetViewerQuery extends Query<IQueryResult> {}
return (
<GetViewerQuery query={GET_VIEWER}>
{({ data }) => {
if (!data || !data.viewer) {
return null;
}
return (
<div>
<img src={data.viewer.avatarUrl} className="avatar" />
<div className="viewer">{data.viewer.name}</div>
<h1>GitHub Search</h1>
</div>
);
}}
</GetViewerQuery>
);
return (
<GetViewerQuery query={GET_VIEWER}>
{({ data, loading }) => {
if (loading) {
return <div className="viewer">Loading ...</div>;
}
...
}}
</GetViewerQuery>
);
return (
<GetViewerQuery query={GET_VIEWER}>
{({ data, loading, error }) => {
if (error) {
return <div className="viewer">{error.toString()}</div>;
}
...
}}
</GetViewerQuery>
);
This Apollo implementation is really elegant. It's clever how the Query component makes the web request at the correct point in the component lifecycle and allows us to feed the rest of the component tree the data.
In the next section, we'll continue to enhance our app with Apollo.
In this section, we'll add a component to search for a GitHub repository and return some information about it:
import * as React from "react";
import gql from "graphql-tag";
import { ApolloClient } from "apollo-boost";
interface IProps {
client: ApolloClient<any>;
}
const RepoSearch: React.SFC<IProps> = props => {
return null;
}
export default RepoSearch;
import RepoSearch from "./RepoSearch";
<ApolloProvider client={client}>
<div className="App">
<header className="App-header">
<Header />
</header>
<RepoSearch client={client} />
</div>
</ApolloProvider>
Our repository search component is nicely set up now. In the next, section we can implement a search form.
Let's implement a search form that allows the user to supply an organization name and repository name:
interface ISearch {
orgName: string;
repoName: string;
}
const RepoSearch: React.SFC<IProps> = props => {
const [search, setSearch]: [
ISearch,
(search: ISearch) => void
] = React.useState({
orgName: "",
repoName: ""
});
return null;
}
return (
<div className="repo-search">
<form onSubmit={handleSearch}>
<label>Organization</label>
<input
type="text"
onChange={handleOrgNameChange}
value={search.orgName}
/>
<label>Repository</label>
<input
type="text"
onChange={handleRepoNameChange}
value={search.repoName}
/>
<button type="submit">Search</button>
</form>
</div>
);
We've referenced a few bits that aren't implemented yet. So, we'll implement this one by one.
.repo-search {
margin: 30px auto;
width: 300px;
font-family: Arial;
font-size: 16px;
text-align: left;
}
.repo-search label {
display: block;
margin-bottom: 3px;
font-size: 14px;
}
.repo-search input {
display: block;
margin-bottom: 10px;
font-size: 16px;
color: #676666;
width: 100%;
}
.repo-search button {
display: block;
margin-bottom: 20px;
font-size: 16px;
}
const handleOrgNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch({ ...search, orgName: e.currentTarget.value });
};
const handleRepoNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch({ ...search, repoName: e.currentTarget.value });
};
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// TODO - make GraphQL query
};
We call preventDefault on the event argument to stop a full postback occurring.
So, that's the search form started. We'll implement the GraphQL query in the next section.
We are now at the point where we need to make the GraphQL query to do the actual search:
interface IRepo {
id: string;
name: string;
description: string;
viewerHasStarred: boolean;
stargazers: {
totalCount: number;
};
issues: {
edges: [
{
node: {
id: string;
title: string;
url: string;
};
}
];
};
}
This is the structure we got back from the GitHub GraphQL Explorer in an earlier section.
const defaultRepo: IRepo = {
id: "",
name: "",
description: "",
viewerHasStarred: false,
stargazers: {
totalCount: 0
},
issues: {
edges: [
{
node: {
id: "",
title: "",
url: ""
}
}
]
}
};
interface IQueryResult {
repository: IRepo;
}
const GET_REPO = gql`
query GetRepo($orgName: String!, $repoName: String!) {
repository(owner: $orgName, name: $repoName) {
id
name
description
viewerHasStarred
stargazers {
totalCount
}
issues(last: 5) {
edges {
node {
id
title
url
publishedAt
}
}
}
}
}
`;
This is the query we made in the GitHub GraphQL Explorer in an earlier section. Unlike our previous queries, this one has parameters that we'll need to include when we execute the query a little later.
const [repo, setRepo]: [
IRepo,
(repo: IRepo) => void
] = React.useState(defaultRepo);
const [searchError, setSearchError]: [
string,
(searchError: string) => void
] = React.useState("");
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSearchError("");
};
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSearchError("");
props.client
.query<IQueryResult>({
query: GET_REPO
});
};
.query<IQueryResult>({
query: GET_REPO,
variables: { orgName: search.orgName, repoName: search.repoName }
})
props.client
.query<IQueryResult>( ... )
.then(response => {
setRepo(response.data.repository);
});
props.client
.query<IQueryResult>(...)
.then(...)
.catch(error => {
setSearchError(error.message);
});
If we try a search in the running app, the query will be made okay, but we are not showing the results yet. Let's do that in the next section.
Let's render the data we have got from the repository query:
return (
<div className="repo-search">
<form ...>
...
</form>
{repo.id && (
<div className="repo-item">
<h4>
{repo.name}
{repo.stargazers ? ` ${repo.stargazers.totalCount}
stars` : ""}
</h4>
<p>{repo.description}</p>
</div>
)}
</div>
);
...
<p>{repo.description}</p>
<div>
Last 5 issues:
{repo.issues && repo.issues.edges ? (
<ul>
{repo.issues.edges.map(item => (
<li key={item.node.id}>{item.node.title}</li>
))}
</ul>
) : null}
</div>
{repo.id && (
...
)}
{searchError && <div>{searchError}</div>}
.repo-search h4 {
text-align: center;
}
If we search for a repository, we should now see information about the repository rendered:

We are now getting comfortable querying a GraphQL server with Apollo. In the next section, we'll tackle mutations.
Let's allow users to star a GitHub repository in our app. This will involve sending a mutation via Apollo:
import { Mutation } from "react-apollo";
const STAR_REPO = gql`
mutation($repoId: ID!) {
addStar(input: { starrableId: $repoId }) {
starrable {
stargazers {
totalCount
}
}
}
}
`;
<p>{repo.description}</p>
<div>
{!repo.viewerHasStarred && (
<Mutation
mutation={STAR_REPO}
variables={{ repoId: repo.id }}
>
{() => (
// render Star button that invokes the mutation when
clicked
)}
</Mutation>
)}
</div>
<div>
Last 5 issues:
...
</div>
<Mutation
...
>
{(addStar) => (
<div>
<button onClick={() => addStar()}>
Star!
</button>
</div>
)}
</Mutation>
)}
<Mutation
...
>
{(addStar, { loading }) => (
<div>
<button disabled={loading} onClick={() => addStar()}>
{loading ? "Adding ..." : "Star!"}
</button>
</div>
)}
</Mutation>
<Mutation
...
>
{(addStar, { loading, error }) => (
<div>
<button ...>
...
</button>
{error && <div>{error.toString()}</div>}
</div>
)}
</Mutation>
If we try to add a star to a repository, the star should be successfully added. We can go to the GitHub repository in github.com to verify this.
So, we are really getting to grips with Apollo now that we've implemented both queries and a mutation. There is one thing that was a little odd, though, that we may have spotted. The number of stars doesn't update in our app after we star a repository. Even if we search for the repository again, the number of stars is the number before we started it. However, if we refresh the browser and search for the repository again, we do get the correct number of stars. So, what's going on here? We'll find out in the next section.
We ended the last section with a bit of a mystery. Why aren't we getting the up-to-date number of stars in a repository search after we've started it? The answer is that Apollo caches the repository data after the initial search. When the same query is executed, it gets the results from its cache, rather than getting the data from the GraphQL server.
Let's double-check that this is the case:


So, our ApolloClient that we configured using apollo-boost automatically caches queries in an in-memory cache. In the next section, we'll learn how to clear the cache so that our app shows the correct number of stars after a repository has been starred.
We need a way of clearing the cached query result after a mutation has happened. One way of doing this is to use the refetchQueries prop on the Mutation component:
<Mutation
mutation={STAR_REPO}
variables={{ repoId: repo.id }}
refetchQueries={[
{
query: GET_REPO,
variables: {
orgName: search.orgName,
repoName: search.repoName
}
}
]}
>
...
</Mutation>
So, the cache was cleared but the experience still isn't ideal. Ideally, we want the number of stars to be updated immediately after the Star! button is clicked.
If we think through what we have just done, we are trying to bypass the cache. However, the cache is in place to help our app perform well.
So, this approach doesn't feel great. The user experience still isn't ideal, and we have just made our app less performant. There must be a better way! We'll explore a different approach in the next section.
Let's think through the problem one more time:
So, let's give this a go and update the cache after the mutation has finished:
<Mutation
mutation={STAR_REPO}
update={cache => {
// Get the cached data
// update the cached data
// update our state
}}
>
...
</Mutation>
<Mutation
...
update={cache => {
const data: { repository: IRepo } | null = cache.readQuery({
query: GET_REPO,
variables: {
orgName: search.orgName,
repoName: search.repoName
}
});
if (data === null) {
return;
}
}}
>
...
</Mutation>
So, the cache has a readQuery function that we can use to get the cached data. If no data is found in the cache then we can exit the function without doing anything else.
update={cache => {
...
if (data === null) {
return;
}
const newData = {
...data.repository,
viewerHasStarred: true,
stargazers: {
...data.repository.stargazers,
totalCount: data.repository.stargazers.totalCount + 1
}
};
}}
update={cache => {
...
const newData = {
...
};
cache.writeQuery({
query: GET_REPO,
variables: {
orgName: search.orgName,
repoName: search.repoName
},
data: { repository: newData }
});
}}
update={cache => {
...
cache.writeQuery(...);
setRepo(newData);
}}
That should be it. If we try to star a repository in our app again, we should see that the number of stars is immediately incremented.
Caching is one of the great features that Apollo gives us out-of-the-box. The update prop on the Mutation component gives us a precise way to update our cache. The refetchQueries prop on the Mutation component is a more heavy-handed and less efficient way of forcing a cache to be updated.
GraphQL shines over REST because it allows us to efficiently get the data we need in the shape we need with far less effort. The GitHub GraphQL Explorer is a great tool for getting comfortable with the syntax. There are two main types of requests we can make to a GraphQL server:
Queries allow us to specify the objects and fields we want in the response. We can rename them by using aliases. We can parameterize a query by defining variables. We can give variables types and specify whether each one is required or not with ! at the end. There are query features that we didn't cover in this chapter, such as conditionally including fields and the powerful paging capability. In summary, it's an extremely powerful query language!
Mutations share some of the same features as queries, such as being able to pass parameters into them. It's great how we get to control what data is included in the response as well.
GraphQL operates over HTTP with HTTP POST requests to a single URL. The HTTP body contains the query or mutation information. We can use an HTTP client to interact with a GraphQL server, but we'll probably be more productive with a library like Apollo that is built specifically to interact with GraphQL servers.
React Apollo is a set of React bits and pieces that work with the core Apollo library. It gives us nice Query and Mutation React components for including queries and mutations right in our JSX, making our code arguably easier to read. Before we can use these components, we need to set up our ApolloClient object with the URL to the GraphQL server and any credentials. We also need to include an ApolloProvider component high in our component tree, above all the components that need GraphQL data.
Caching is switched on out-of-the-box when we scaffold our project with apollo-boost. The Mutation component gives us update and refetchQueries props to manage cache updates.
All in all, GraphQL is a very productive way to interact with backends, and it works really nicely with React and TypeScript apps.
So, we've learned many different aspects of React and TypeScript in this book so far. One big topic that we haven't covered yet is how we can robustly test the apps we build. We'll cover this in the next chapter.
Let's have a go at some questions to test our knowledge on what we have just learned:
The following links are good resources of further information on GraphQL in general, along with React and Apollo:
Building a robust suite of unit tests that catches real bugs and doesn't flag false positives as we refactor our code is one of the hardest tasks we do as software developers. Jest is a great testing tool that helps us meet this challenge, as we'll find out in this chapter.
Perhaps the easiest bits of an app to unit test are pure functions, because there are no side effects to deal with. We'll revisit the validator functions we built in Chapter 7, Working with Form, and implement some unit tests against them in order to learn how to unit test pure functions.
Unit testing components is the most common type of unit test we'll be carrying out while building our apps. We'll learn about it in detail, and leverage a library to help us implement tests that don't unnecessarily break when we refactor our code.
We'll learn what snapshot testing is, and how we can leverage it to implement our tests quicker. Snapshots can be used for testing pure functions as well as components, so they are a very useful tool to have at our disposal.
Mocking is a challenging topic because if we mock too much, we aren't really testing our app. However, there are certain dependencies that make sense to mock, such as a REST API. We'll revisit the app we built in Chapter 9, Interacting with Restful APIs, in order to implement some unit tests against it and learn about mocking.
When implementing a suite of unit tests for our app, it is useful to know which bits we've tested and which bits we haven't. We'll learn how to use a code coverage tool to help us quickly identify areas of our app that need more unit tests.
The following topics will be covered in this chapter:
We use the following technologies in this chapter:
We'll start our unit testing journey in this section by implementing a unit test on a pure function.
The fact that these functions only depend on their parameter values makes them straightforward to unit test.
We are going to implement a unit test on the required validator function we created in our Form component in the React shop we built. If you haven't already, open this project in Visual Studio Code.
We are going to use Jest, which is very popular for unit testing React apps, as our unit testing framework. Luckily the create-react-app tool installs and configures this for us when creating a project. So, Jest is ready to be used in our React shop project.
Let's create our first unit test in our project to test the required function in Form.tsx:
import { required, IValues } from "./Form";
test("When required is called with empty title, 'This must be populated' should be returned", () => {
// TODO: implement the test
});
The test function takes in two parameters:
test("When required called with title being an empty string, an error should be 'This must be populated'", () => {
const values: IValues = {
title: ""
};
const result = required("title", values);
// TODO: check the result is correct
});
test("When required called with title being an empty string, an error should be 'This must be populated'", () => {
const values: IValues = {
title: ""
};
const result = required("title", values);
expect(result).toBe("This must be populated");
});
We pass the variable we are checking into the expect function. We then chain a toBe matcher function onto this, which checks that the result from the expect function is the same as the parameter supplied to the toBe function.
npm test
This starts the Jest test runner in watch mode, which means that it will continuously run, executing tests when we change the source files.
Jest will eventually find our test file, execute our test, and output the result to the terminal, as follows:

expect(result).toBe("This must be populatedX");
When we save the test file, Jest automatically executes the test and outputs the failure to the terminal, as follows:

Jest gives us valuable information about the failure. It tells us this:
This information helps us quickly resolve test failures.
expect(result).toBe("This must be populated");
When we save the change, the test should now pass.
After Jest executes our tests, it provides us with the following options:
> Press f to run only failed tests.
> Press o to only run tests related to changed files.
> Press p to filter by a filename regex pattern.
> Press t to filter by a test name regex pattern.
> Press q to quit watch mode.
> Press Enter to trigger a test run.
These options let us specify what tests should be executed, which is really useful as the number of tests grows. Let's explore some of these options:


Our test in Form.test.tsx will then be executed.

Our test on the required function will then be executed.
We have a good handle on the options available to execute our tests now.
As we implement more unit tests, it is useful to add some structure to the unit test results so that we can read them more easily. There is a Jest function called describe that we can use to group the results of certain tests together. It may make reading test results easier if all the tests for a function are grouped together.
Let's do this and refactor the unit test we created earlier, using the describe function in Jest:
describe("required", () => {
test("When required called with title being an empty string, an error should be 'This must be populated'", () => {
const values: IValues = {
title: ""
};
const result = required("title", values);
expect(result).toBe("This must be populated");
});
});
The describe function takes in two parameters:
When we save our test file, the tests will automatically run, and our improved output is shown in the terminal with our test result under a required heading:

We're starting to get familiar with Jest, having implemented and executed a unit test. In the next section, we will move on to the more complex topic of unit testing components.
Unit testing a component is challenging because a component has dependencies such as the browser's DOM and the React library. How exactly can we render a component in our test code before we do the necessary checks? How can we trigger DOM events when coding a user interaction, such as clicking a button?
We'll answer these questions in this section, by implementing some tests on the ContactUs component we created in our React shop.
We are going to start by creating a unit test to verify that submitting the Contact Us form without filling in the fields results in errors being displayed on the page:
import React from "react";
import ReactDOM from "react-dom";
import { Simulate } from "react-dom/test-utils";
import ContactUs from "./ContactUs";
import { ISubmitResult } from "./Form";
describe("ContactUs", () => {
test("When submit without filling in fields should display errors", () => {
// TODO - implement the test
});
});
test("When submit without filling in fields should display errors", () => {
const handleSubmit = async (): Promise<ISubmitResult> => {
return {
success: true
};
};
const container = document.createElement("div");
ReactDOM.render(<ContactUs onSubmit={handleSubmit} />, container);
// TODO - submit the form and check errors are shown
ReactDOM.unmountComponentAtNode(container);
});
First, we create a container div tag and then render our ContactUs component into this. We have also created a handler for the onSubmit prop, which returns success. The last line in the test cleans up by removing the DOM elements that were created in the test.
ReactDOM.render(<ContactUs onSubmit={handleSubmit} />, container);
const form = container.querySelector("form");
expect(form).not.toBeNull();
Simulate.submit(form!);
// TODO - check errors are shown
ReactDOM.unmountComponentAtNode(container);
Here is the step-by-step description:
Simulate.submit(form!);
const errorSpans = container.querySelectorAll(".form-error");
expect(errorSpans.length).toBe(2);
ReactDOM.unmountComponentAtNode(container);
Let's see this step-by-step:

In this test, Jest is rendering the component in a fake DOM. The form submit event is also simulated, using the simulate function from standard React testing utilities. So, there's a lot of mocking going on in order to facilitate an interactive component test.
Also note that we are referencing internal implementation details in our test code. We reference a form tag, along with a form-error CSS class. What if we later change this CSS class name to contactus-form-error? Our test would break, without there necessarily being a problem with our app.
This is called a false positive, and can make code bases with these kinds of tests very time-consuming to change.
react-testing-library is a set of utilities that helps us write maintainable tests for React components. It focuses heavily on helping us remove implementation details from our test code.
We'll use this library to remove the CSS class references in our test code, and also the tight coupling to React's event system.
Let's install react-testing-library first as a development dependency via the terminal:
npm install --save-dev react-testing-library
After a few seconds, this will be added to our project.
We'll make our first improvement to our test by removing the dependencies on the form-error CSS class. Instead, we will get a reference to the errors via the error text, which is what the user sees onscreen and not an implementation detail:
import { render, cleanup} from "react-testing-library";
test("When submit without filling in fields should display errors", () => {
const handleSubmit = async (): Promise<ISubmitResult> => {
return {
success: true
};
};
const { container, getAllByText } = render(
<ContactUs onSubmit={handleSubmit} />
);
const form = container.querySelector("form");
...
});
We get the container DOM node back in a container variable, along with a getallByText function, which we'll use to get a reference to the displayed errors.
Simulate.submit(form!);
const errorSpans = getAllByText("This must be populated");
expect(errorSpans.length).toBe(2);
afterEach(cleanup);
describe("ContactUs", () => {
test("When submit without filling in fields should display errors", () => {
const handleSubmit = async (): Promise<ISubmitResult> => {
return {
success: true
};
};
const { container, getAllByText } = render(
<ContactUs onSubmit={handleSubmit} />
);
const form = container.querySelector("form");
expect(form).not.toBeNull();
Simulate.submit(form!);
const errorSpans = getAllByText("This must be populated");
expect(errorSpans.length).toBe(2);
});
});
When the test runs, it should still execute okay and the tests should pass.
We are now going to switch to depending on the native event system, rather than React's event system which sits on top of it. This gets us closer to testing what happens when users are using our app, and increases our confidence in our test:
import { render, cleanup, fireEvent } from "react-testing-library";
const { getAllByText, getByText } = render(
<ContactUs onSubmit={handleSubmit} />
);
We can also remove the destructured container variable, as that won't be needed anymore.
const { getAllByText, getByText } = render(
<ContactUs onSubmit={handleSubmit} />
);
const submitButton = getByText("Submit");
fireEvent.click(submitButton);
const errorSpans = getAllByText("This must be populated");
expect(errorSpans.length).toBe(2);
The previous code that referenced the form tag has now been removed.
When the test runs, it still passes.
So, our test references items that the user sees, rather than implementation details, and is far less likely to unexpectedly break.
Now that we have got the gist of how to write robust tests, let's add a second test to check that no validation errors are shown when the form is filled incorrectly:
describe("ContactUs", () => {
test("When submit without filling in fields should display errors", () => {
...
});
test("When submit after filling in fields should submit okay", () => {
// TODO - render component, fill in fields, submit the form and check there are no errors
});
});
test("When submit after filling in fields should submit okay", () => {
const handleSubmit = async (): Promise<ISubmitResult> => {
return {
success: true
};
};
const { container, getByText, getByLabelText } = render(
<ContactUs onSubmit={handleSubmit} />
);
});
Now:
const { container, getByText, getByLabelText } = render(
<ContactUs onSubmit={handleSubmit} />
);
const nameField: HTMLInputElement = getByLabelText(
"Your name"
) as HTMLInputElement;
expect(nameField).not.toBeNull();
const nameField: HTMLInputElement = getByLabelText(
"Your name"
) as HTMLInputElement;
expect(nameField).not.toBeNull();
fireEvent.change(nameField, {
target: { value: "Carl" }
});
We have simulated the user setting the name field as Carl.
const nameField: HTMLInputElement = getByLabelText(
"Your name"
) as HTMLInputElement;
expect(nameField).not.toBeNull();
fireEvent.change(nameField, {
target: { value: "Carl" }
});
const emailField = getByLabelText("Your email address") as HTMLInputElement;
expect(emailField).not.toBeNull();
fireEvent.change(emailField, {
target: { value: "carl.rippon@testmail.com" }
});
Here, we have simulated the user setting the email field as carl.rippon@testmail.com.
fireEvent.change(emailField, {
target: { value: "carl.rippon@testmail.com" }
});
const submitButton = getByText("Submit");
fireEvent.click(submitButton);
{context.errors[name] && context.errors[name].length > 0 && (
<div data-testid="formErrors">
{context.errors[name].map(error => (
<span key={error} className="form-error">
{error}
</span>
))}
</div>
)}
We've given the div tag a data-testid attribute, which we'll use in our test.
fireEvent.click(submitButton);
const errorsDiv = container.querySelector("[data-testid='formErrors']");
expect(errorsDiv).toBeNull();
When the test runs in our suite of tests, we'll find we now have three passing tests.
Isn't referencing the data-testid attribute an implementation detail, though? The user doesn't see or care about the data-testid attribute—this seems to contradict what we said earlier.
It is kind of an implementation detail, but it is specifically for our test. So, an implementation refactor is unlikely to unexpectedly break our test.
In the next section, we are going to add another test, this time using Jest snapshot tests.
A snapshot test is one where Jest compares all the elements and attributes in a rendered component to a previous snapshot of the rendered component. If there are no differences, then the test passes.
We are going to add a test to verify the ContactUs component renders OK, by checking the DOM nodes using a Jest snapshot test:
describe("ContactUs", () => {
...
test("Renders okay", () => {
const handleSubmit = async (): Promise<ISubmitResult> => {
return {
success: true
};
};
const { container } = render(<ContactUs onSubmit={handleSubmit} />);
// TODO - do the snapshot test
});
});
test("Renders okay", () => {
const handleSubmit = async (): Promise<ISubmitResult> => {
return {
success: true
};
};
const { container } = render(<ContactUs onSubmit={handleSubmit} />);
expect(container).toMatchSnapshot();
});
Doing a snapshot test is pretty simple. We pass the DOM node we want to compare into Jest's expect function, and then chain the toMatchSnapshot function after it.
When the test runs, we'll get confirmation that the snapshot has been written in the terminal, as follows:

// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ContactUs Renders okay 1`] = `
<div>
<form
class="form"
novalidate=""
>
<div
class="form-group"
>
<label
for="name"
>
Your name
</label>
<input
id="name"
type="text"
value=""
/>
</div>
...
</form>
</div>
`;
Some of the content is stripped out in this snippet, but we get the gist: we have a copy of every DOM node including their attributes from the container element we passed into the toMatchSnapshot function.
This test is heavily coupled to our implementation, though. So, any change to our DOM structure or attributes will break our test.
<form ...>
<div>{this.props.children}</div>
...
</form>
When the test runs, we'll see confirmation that our test has broken. Jest does a great job of showing us the difference in the terminal:


So, are snapshot tests a good thing or a bad thing? They are volatile because they are tightly coupled to the implementation of a component. However they are super-easy to create, and when they do break, Jest does a great job of highlighting the problem area and allowing us to efficiently correct the test snapshot. They are well worth a try to see if your team gains value from them.
We have learned a lot already in this chapter about unit testing React and TypeScript apps. Next up, we'll learn how we can mock dependencies.
Mocking a component's dependencies can make the component easier to test. However, if we mock too many things, is the test really verifying that the component will work in our real app?
Establishing what to mock is one of the hardest tasks when writing unit tests. There are some things that make a lot of sense to mock, though, such as REST APIs. A REST API is a pretty fixed contract between the frontend and backend. Mocking a REST API also allows our tests to run nice and fast.
In this section, we'll eventually learn how to mock REST API calls made with axios. First, though, we'll learn about Jest's function mocking feature.
We are going to make another improvement to the test that verified that submitting the Contact Us form without filling in the fields results in errors being displayed on the page. We are going to add an additional check, to make sure the submit handler is not executed:
const handleSubmit = jest.fn();
Our test will run correctly, as it did before, but this time Jest is mocking the function for us.
const errorSpans = container.querySelectorAll(".form-error");
expect(errorSpans.length).toBe(2);
expect(handleSubmit).not.toBeCalled();
This is really nice, because we've not only simplified our submit handler function, but we've also really easily added a check to verify that it hasn't been called.
Let's move on to the second test we implemented, which verified that a valid Contact Us form was submitted okay:
const handleSubmit = jest.fn();
const errorsDiv = container.querySelector("[data-testid='formErrors']");
expect(errorsDiv).toBeNull();
expect(handleSubmit).toBeCalledTimes(1);
When the test executes, it should still pass.
expect(handleSubmit).toBeCalledTimes(1);
expect(handleSubmit).toBeCalledWith({
name: "Carl",
email: "carl.rippon@testmail.com",
reason: "Support",
notes: ""
});
Again, when the test executes, it should still pass.
So, by letting Jest mock our submit handler, we've quickly added a few valuable additional checks to our tests.
We are going to move to the project we created in Chapter 9, Interacting with Restful APIs. We are going to add a test that verifies the posts are rendered on the page correctly. We'll mock the JSONPlaceholder REST API so we are in control of the data that is returned, and so that our test will execute nicely and quickly:
npm install axios-mock-adapter --save-dev
npm install react-testing-library --save-dev
import { render, cleanup, waitForElement } from "react-testing-library";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
afterEach(cleanup);
describe("App", () => {
test("When page loads, posts are rendered", async () => {
// TODO - render the app component with a mock API and check that the posts in the rendered list are as expected
});
});
Note that the arrow function is marked with the async keyword. This is because we'll eventually make an asynchronous call in our test.
test("When page loads, posts are rendered", async () => {
const mock = new MockAdapter(axios);
mock.onGet("https://jsonplaceholder.typicode.com/posts").reply(200, [
{
userId: 1,
id: 1,
title: "title test 1",
body: "body test 1"
},
{
userId: 1,
id: 2,
title: "title test 2",
body: "body test 2"
}
]);
});
We use the onGet method to define the response HTTP status code and body we want when the URL to get the posts is called. So, the call to the REST API should return two posts containing our test data.
{this.state.posts.length > 0 && (
<ul className="posts" data-testid="posts">
...
</ul>
)}
mock.onGet("https://jsonplaceholder.typicode.com/posts").reply(...);
const { getByTestId } = render(<App />);
const { getByTestId } = render(<App />);
const postsList: any = await waitForElement(() => getByTestId("posts"));
The waitForElement function takes in an arrow function as a parameter, which in turn returns the element we are waiting for. We use the getByTestId function to get the posts list, which finds it using its data-testid attribute.
const postsList: any = await waitForElement(() => getByTestId("posts"));
expect(postsList).toMatchSnapshot();
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "es2015"],
...
},
"include": ["src"]
}
When the test executes, the snapshot is created. If we inspect the snapshot, it will contain the two list items containing data that we told the REST API to return.
We've learned about some great features in Jest and react-testing-library that help us write maintainable tests on pure functions and React components.
How can we tell what bits of our app are covered by unit tests, though—and, more importantly, what bits are uncovered? We'll find out in the next section.
Code coverage is how we refer to how much of our app code is covered by unit tests. As we write our unit tests, we'll have a fair idea of what code is covered and what code is not covered, but as the app grows and time passes, we'll lose track of this.
Jest comes with a great code coverage tool, so we don't have to keep what is covered in our heads. In this section, we'll use this to discover the code coverage in the project we worked on in the previous section, where we mocked axios:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test-coverage": "react-scripts test --coverage",
"eject": "react-scripts eject"
},
npm run test-coverage
After a few seconds, Jest will render some nice high-level coverage statistics on each file in the terminal:


We see the same information as presented in the terminal.
What do these four columns of statistics mean?
let name = "Carl"; console.log(name);

The 1x with a green background to the left of the lines of code indicates that those lines have been executed by our tests once. The code highlighted in red is code that isn't covered by our tests.
So, getting coverage statistics and identifying additional tests that we may want to implement is pretty easy. It's something well worth using to give us confidence that our app is well-tested.
In this chapter, we learned how to test pure functions written in TypeScript using Jest. We simply execute the function with the parameters we want to test, and use Jest's expect function chained with one of Jest's matcher functions, such as toBe, to validate the result.
We looked at how to interact with Jest's test runner, and how to apply filters so that only the tests we are focusing on are executed. We learned that testing React and TypeScript components is more involved than testing pure functions, but Jest and react-testing-library give us a great deal of help.
We also learned how to render a component using the render function, and how to interact with and inspect elements using various functions such as getByText and getLabelByText from react-testing-library.
We learned that we can easily test asynchronous interactions using the waitForElement function in react-testing-library as well. We now understand the benefit of not referencing implementation details in our tests, which will help us build more robust tests.
We also discussed Jest's clever snapshot testing tool. We looked at how these tests can regularly break, but also why they are incredibly easy to create and change.
The ability to mock and spy into functions is another great Jest feature we now know about. Checking that functions for component event handlers have been called with the right parameters can really add value to our tests.
We discussed the axios-mock-adapter library which we can use for mocking axios REST API requests. This allows us to easily test container components that interact with RESTful APIs.
We now know how to quickly identify the additional tests that we need to implement to give us confidence that our app is well-tested. We created an npm script command to enable us to do this, using react-scripts and the --coverage option.
Overall, we now have the knowledge and the tools to robustly create unit tests for our apps with Jest.
Jasmine and Mocha are two popular alternative testing frameworks to Jest. The big advantage of Jest is that it is configured by create-react-app to work out the box. We would have to configure Jasmine and Mocha manually if we wanted to use them. Jasmine and Mocha are worth considering, though, if your team is already experienced with either of these tools, rather than learning another testing framework.
Enzyme is another popular library used with Jest to test React apps. It supports shallow rendering, which is a way of rendering only the top-level elements in a component and not child components. It is well worth exploring, but remember that the more we mock, the further from the truth we get, and the less confidence we have that our app is well-tested.
interface IPerson {
id: number;
name: string;
}
We want to check that the person variable is { id: 1, name: "bob" }. How can we do this with Jest matcher functions?
const { container } = render(<SimpleList data={["Apple", "Banana", "Strawberry"]} onItemSelect={handleListItemSelect} />);
How can we use a Jest mock function for handleListItemSelect and check that it is called?
The following resources are useful for finding more information on unit testing React and TypeScript apps:
const flag = false;
flag will be inferred as the boolean type.
The main difference is that type aliases can't be extended or implemented from, like you can with interfaces.
class Product {
constructor(public name: string, public unitPrice: number) {}
}
let table = new Product();
table.name = "Table";
table.unitPrice = 700;
The constructor requires name and unitPrice to be passed. Here are two ways to resolve the problem.
Pass the values in the constructor:
let table = new Product("Table", 700);
Make the parameters optional:
class Product {
constructor(public name?: string, public unitPrice?: number) {}
}
This should be es5 because IE11 only supports up to ES5 features.
Yes! We can use the --allowJS setting to get the compiler to transpile JavaScript files.
We can use tslint and the "no-console" rule to enforce this. This would be the rule in tslint.json:
{
"rules": {
"no-console": true
}
}
function drawPoint(x: number, y: number, z: number) {
...
}
We also have the following point variable:
const point: [number, number, number] = [100, 200, 300];
How can we call the drawPoint function in a terse manner?
drawPoint(...point);
drawPoint(1, 2, 3);
Internally in the implementation of drawPoint we draw the point from a tuple data type, [number, number, number]. How can we define the method parameter(s) with the required tuple?
function drawPoint(...point: [number, number, number]) {
...
}
function drawPoint(...point: [number, number, number?]) {
...
}
function getData(resource: string): any {
const data = ... // call the web API
if (resource === "person") {
data.fullName = `${data.firstName} ${data.surname}`;
}
return data;
}
How can we make getData more type-safe by leveraging the unknown type?
class Person {
firstName: string;
surname: string;
fullName: string;
}
function getData(resource: string): unknown {
const data = {};
if (data instanceof Person) {
data.fullName = `${data.firstName} ${data.surname}`;
}
return data;
}
tsc --build ... --dry --verbose
"rules": {
"no-debugger": false,
"no-console": false,
},
<button>{this.props.buttonLabel}</button>
Use a ? before the type annotation in the interface for the props:
interface IProps {
buttonLabel?: string
}
Implement a static defaultProps object at the top of the class component:
public static defaultProps = {
buttonLabel: "Do it"
};
{this.state.doItVisible && <button>{this.props.buttonLabel}</button>}
<button onClick={this.handleDoItClick}>{this.props.buttonLabel}</button>
private handleDoItClick = () => {
// TODO: some stuff!
};
private handleDoItClick = () => {
this.setState({doItDisabled: true})
};
<button disabled={this.state.doItDisabled}>{this.props.buttonLabel}</button>
No
componentDidMount
componentWillUnmount
const count, setCount = React.useState(10);
const decrement = () => {
// TODO - reduce count by 1
};
How can this be implemented?
const decrement = () => {
setCount(count - 1);
};
<Route path="/customers" component={CustomersPage} />
Will the CustomersPage component render when the page is "/customers"?
Yes
We can use the exact attribute:
<Route exact={true} path="/customers" component={CustomersPage} />
<Route exact={true} path="/customers/:customerId" component={CustomerPage} />
How can we catch paths that don't exist so that we can inform the user?
Make sure all the Route components are wrapped in a Switch component. We can then add a Route to a component that renders a not found message to the user as the last Route in Switch:
<Switch>
<Route path="/customers/:customerId" component={CustomerPage} />
<Route exact={true} path="/customers" component={CustomersPage} />
<Route component={NotFoundPage} />
</Switch>
First we need the props type to be RouteComponentProps in our class:
import { RouteComponentProps } from "react-router-dom";
class CustomersPage extends React.Component<RouteComponentProps, IState> { ... }
We can the use URLSearchParams to get the search query parameter and do the search in the componentDidMount life cycle method:
public componentDidMount() {
const searchParams = new URLSearchParams(props.location.search);
const search = searchParams.get("search") || "";
const products = await ... // make web service call to do search
this.setState({ products });
}
We can use the Redirect component to redirect the old paths to the new paths:
<Switch>
<Route path="/clients/:customerId" component={CustomerPage} />
<Route exact={true} path="/clients" component={CustomersPage} />
<Redirect from="/customers/:customerId" to="/clients/:customerId" />
<Redirect exact={true} from="/customers" to="/clients" />
<Route component={NotFoundPage} />
</Switch>
interface ICourseMark {
courseName: string;
grade: string;
}
We can use this interface as follows:
const geography: ICourseMark = {
courseName: "Geography",
grade: "B"
}
The grades can only be A, B, C, or D. How can we create a stronger typed version of the grade property in this interface?
We can use a union type:
interface ICourseMark {
courseName: string;
grade: "A" | "B" | "C" | "D";
}
function isNumberPopulated(field: number): boolean {
return field !== null && field !== undefined;
}
function isStringPopulated(field: string): boolean {
return field !== null && field !== undefined && field !== "";
}
How can we combine these into a single function called isPopulated with signature overloads?
We can use overload signatures and then a union type for field in the main function. We can then use the typeof type guard in the function to deal with the different branches of logic:
function isPopulated(field: number): boolean
function isPopulated(field: string): boolean
function isPopulated(field: number | string): boolean {
if (typeof field === "number") {
return field !== null && field !== undefined;
} else {
return field !== null && field !== undefined && field !== "";
}
}
We can use a generic function with a typeof type guard for the special branch of code for strings:
function isPopulated<T>(field: T): boolean {
if (typeof field === "string") {
return field !== null && field !== undefined && field !== "";
} else {
return field !== null && field !== undefined;
}
}
type Stages = {
pending: 'Pending',
started: 'Started',
completed: 'Completed',
};
How can we programmatically turn this into the union type 'Pending' | 'Started' | 'Completed'?
We can use the keyof keyword:
type StageUnion = keyof Stages
type Grade = 'gold' | 'silver' | 'bronze';
How can we programmatically create the following type?
type GradeMap = {
gold: string;
silver: string;
bronze: string
};
We can map the type as follows:
type GradeMap = { [P in Grade]: string }
A property called children
As many components as we like that are under the provider component in the component hierarchy
The render props pattern
As many as we like
1
export const getProducts = async (): Promise<IProduct[]> => {
await wait(1000);
return products;
};
Can you use this to implement a loader spinner on the products page by consuming the withLoader HOC.
First we split ProductPage into a container and presentational component. The presentational component will render the product list exporting it wrapped in the withLoader HOC:
import * as React from "react";
import { Link } from "react-router-dom";
import { IProduct } from "./ProductsData";
import withLoader from "./withLoader";
interface IProps {
products: IProduct[];
search: string;
}
const ProductList: React.SFC<IProps> = props => {
const { products, search } = props;
return (
<ul className="product-list">
{products.map(product => {
if (
!search ||
(search &&
product.name.toLowerCase().indexOf(search.toLowerCase()) > -1)
) {
return (
<li key={product.id} className="product-list-item">
<Link to={`/products/${product.id}`}>{product.name}</Link>
</li>
);
} else {
return null;
}
})}
</ul>
);
};
export default withLoader(ProductList);
We then can consume this in ProductPage as follows in its render method:
public render() {
return (
<div className="page-container">
<p>
Welcome to React Shop where you can get all your tools for ReactJS!
</p>
<ProductList
loading={this.state.loading}
products={this.state.products}
search={this.state.search}
/>
</div>
);
}
<Loader loading={this.state.loading}>
<div>
The content for my component ...
</div>
</Loader>
If so, have a go at implementing it?
Yes
import * as React from "react";
interface IProps {
loading: boolean;
}
const Loader: React.SFC<IProps> = props =>
props.loading ? (
<div className="loader-overlay">
<div className="loader-circle-wrap">
<div className="loader-circle" />
</div>
</div>
) : props.children ? (
<React.Fragment>{props.children}</React.Fragment>
) : null;
export default Loader;
interface IFieldProps {
...
type?: "Text" | "Email" | "Select" | "TextArea" | "Number";
}
{(type === "Text" || type === "Email" || type === "Number") && (
<input
type={type.toLowerCase()}
id={name}
value={context.values[name]}
onChange={e => handleChange(e, context)}
onBlur={e => handleBlur(e, context)}
/>
)}
Add the following field immediately after the notes field:
<Form.Field name="urgency" label="How urgent is a response?" type="Number" />
Add the following function in Form.tsx:
export const between: Validator = (
fieldName: string,
values: IValues,
bounds: { lower: number; upper: number }
): string =>
values[fieldName] &&
(values[fieldName] < bounds.lower || values[fieldName] > bounds.upper)
? `This must be between ${bounds.lower} and ${bounds.upper}`
: "";
import { between, Form, ISubmitResult, IValues, minLength, required } from "./Form";
validationRules={{
email: { validator: required },
name: [{ validator: required }, { validator: minLength, arg: 3 }],
urgency: [{ validator: between, arg: { lower: 1, upper: 10 } }]
}}
interface ITouched {
[key: string]: boolean;
}
interface IState {
touched: ITouched;
...
}
constructor(props: IFormProps) {
super(props);
const errors = {};
const touched = {};
Object.keys(props.defaultValues).forEach(fieldName => {
errors[fieldName] = [];
touched[fieldName] = false;
});
this.state = {
errors,
submitted: false,
submitting: false,
touched,
values: props.defaultValues
};
}
private setValue = (fieldName: string, value: any) => {
const newValues = { ...this.state.values, [fieldName]:
value };
const newTouched = { ...this.state.touched, [fieldName]:
true };
this.setState({ values: newValues, touched: newTouched });
};
private validate = (fieldName: string, value: any): string[] => {
if (!this.state.touched[fieldName]) {
return [];
}
...
};
The type property is required in the action objects and must be called type.
As many as we like! It needs to include at least one for the type property. It can then include as many other properties as we need in order for the reducer to change the state but this is generally lumped in one additional property. So, generally an action will have one or two properties.
An action creator is a function that returns an action object. Components invoke these functions in order to make a change to the state in the store.
By default, a Redux store can't manage asynchronous action creators. Middleware needs to be added to the Redux store in order to facilitate asynchronous action creators. Redux Thunk is the middleware we added to do this.
Yes! We could have created our own middleware. There are other well-established libraries that we could have used as well, such as Redux Saga.
export const basketReducer: Reducer<IBasketState, BasketActions> = (
state = initialBasketState,
action
) => {
switch (action.type) {
case BasketActionTypes.ADD: {
state.products.push(action.product);
}
}
return state || initialBasketState;
};
This mutates the product's state directly and makes the function impure. This is because we have changed the state argument, which lives outside the scope of our function. Breaking this rule, in this case, results in the basket summary not incrementing on the rendered page when the Add to basket button is clicked.
try {
setInterval(() => {
throw new Error("Oops");
}, 1000);
} catch (ex) {
console.log("Sorry, there is a problem", ex);
}
We'd get a message saying that an uncaught error (Oops) has occurred. The console.log statement wouldn't be reached.
fetch("https://jsonplaceholder.typicode.com/posts/9999")
.then(response => {
console.log("HTTP status code", response.status);
return response.json();
})
.then(data => console.log("Response body", data))
.catch (error => console.log("Error", error));

The key thing is that an HTTP error doesn't get handled in the catch method with the fetch function.
axios
.get("https://jsonplaceholder.typicode.com/posts/9999")
.then(response => {
console.log("HTTP status code", response.status);
})
.catch(error => {
console.log("Error", error.response.status);
});

The key thing is that an HTTP error does get handled in the catch method with axios.
If we are targeting modern browsers (and not IE) and only require simple REST API interaction then fetch is arguably more favorable than axios because our code isn't dependent on third-party code. It will also probably run a little faster because there is less non-native code being executed.
axios.get("https://jsonplaceholder.typicode.com/posts/1")
axios
.get("https://jsonplaceholder.typicode.com/posts/1", {
headers: {
"Authorization": `Bearer ${token}`
}
});
axios.put("https://jsonplaceholder.typicode.com/posts/1", {
title: "corrected title",
body: "some stuff"
});
The body hasn't changed though – it's just the title we want to update. How can we change this to a PATCH request to make this REST call more efficient?
axios.patch("https://jsonplaceholder.typicode.com/posts/1", {
title: "corrected title"
});
React.useEffect(() => {
axios
.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(...)
.catch(...);
});
What is wrong with the preceding code?
The second parameter in the useEffect function is missing, which means the REST API will be called every time the component is rendered. An empty array should be supplied as the second parameter so that the REST API is only called on the first render:
React.useEffect(() => {
axios
.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(...)
.catch(...);
}, []);
query {
repository (owner:"facebook", name:"react") {
issues(last: 5, states:[OPEN]) {
edges {
node {
title
url
}
}
}
}
}
query ($lastCount: Int = 5) {
repository (owner:"facebook", name:"react") {
issues(last: $lastCount, states: [OPEN]) {
edges {
node {
title
url
}
}
}
}
}
mutation ($repoId: ID!) {
removeStar(input: { starrableId: $repoId }) {
starrable {
stargazers {
totalCount
}
}
}
}
The HTTP body
The HTTP body
Create another component that extends Query passing in a type for the result as a generic parameter:
class MyQuery extends Query<IResult> {}
We can then use the MyQuery component in our JSX.
On
The update prop.
expect(result).not.toBeNull()
interface IPerson {
id: number;
name: string;
}
We want to check that the person variable is { id: 1, name: "bob" }. How can we do this with Jest matcher functions?
expect(person).not.toBeEqual({ id: 1, name: "bob" });
Yes:
expect(person).toMatchSnapshot();
const { container } = render(<SimpleList data={["Apple", "Banana", "Strawberry"]} onItemSelect={handleListItemSelect} />);
How can we use a Jest mock function for handleListItemSelect and check that it is called?
const handleListItemSelect = jest.fn();
const { container } = render(<SimpleList data={["Apple", "Banana", "Strawberry"]} onItemSelect={handleListItemSelect} />);
// TODO - select the list item
expect(handleListItemSelect).toBeCalledTimes(1);
expect(handleListItemSelect).toBeCalledWith("Banana");
const checkbox = getByLabelText("Banana") as HTMLInputElement;
fireEvent.change(checkbox, {
target: { checked: true }
});
test("When the post GET request errors when the page is loaded, an error is shown", async () => {
const mock = new MockAdapter(axios);
mock.onGet("https://jsonplaceholder.typicode.com/posts").reply(404);
const { getByTestId } = render(<App />);
const error: any = await waitForElement(() => getByTestId("error"));
expect(error).toMatchSnapshot();
});
A test ID needs to be added to the App component code:
{this.state.error && <p className="error" data-testid="error">{this.state.error}</p>}
If you enjoyed this book, you may be interested in these other books by Packt:
React Cookbook
Carlos Santana Roldan
ISBN: 9781783980727
Full-Stack React Projects
Shama Hoque
ISBN: 9781788835534
Please share your thoughts on this book with others by leaving a review on the site that you bought it from. If you purchased the book from Amazon, please leave us an honest review on this book's Amazon page. This is vital so that other potential readers can see and use your unbiased opinion to make purchasing decisions, we can understand what our customers think about our products, and our authors can see your feedback on the title that they have worked with Packt to create. It will only take a few minutes of your time, but is valuable to other potential customers, our authors, and Packt. Thank you!