Useful Patterns by TypeScript Version

TypeScript Versions often introduce new ways to do things; this section helps current users of React + TypeScript upgrade TypeScript versions and explore patterns commonly used by TypeScript + React apps and libraries. This may have duplications with other sections; if you spot any discrepancies, file an issue!

TypeScript version guides before 2.9 are unwritten, please feel free to send a PR! Apart from official TS team communication we also recommend Marius Schulz's blog for version notes. For more TypeScript history, see A Brief History of TypeScript Types and A Brief History of DefinitelyTyped

TypeScript 2.9

[Release Notes | Blog Post]

  1. Type arguments for tagged template strings (e.g. styled-components):
export interface InputFormProps {
foo: string; // this is understood inside the template string below
export const InputForm = styledInput<InputFormProps>`
${({ themeName }) => (themeName === "dark" ? "black" : "white")};
border-color: ${({ foo }) => (foo ? "red" : "black")};
  1. JSX Generics

Helps with typing/using generic components:

// instead of
render={(props: FormikProps<Values>) => {
/* your code here ... */
// usage
render={(props) => {
/* your code here ... */
<MyComponent<number> data={12} />;

More info:

TypeScript 3.0

[Release Notes | Blog Post]

  1. Typed rest parameters for writing arguments of variable length:
// `rest` accepts any number of strings - even none!
function foo( string[]) {
// ...
foo("hello"); // works
foo("hello", "world"); // also works
  1. Support for propTypes and static defaultProps in JSX using LibraryManagedAttributes:
export interface Props {
name: string;
export class Greet extends React.Component<Props> {
render() {
const { name } = this.props;
return <div>Hello ${name.toUpperCase()}!</div>;
static defaultProps = { name: "world" };
// Type-checks! No type assertions needed!
let el = <Greet />;
  1. new Unknown type

For typing API's to force type checks - not specifically React related, however very handy for dealing with API responses:

interface IComment {
date: Date;
message: string;
interface IDataService1 {
getData(): any;
let service1: IDataService1;
const response = service1.getData();
response.a.b.c.d; // RUNTIME ERROR
// ----- compare with -------
interface IDataService2 {
getData(): unknown; // ooo
let service2: IDataService2;
const response2 = service2.getData();
// response2.a.b.c.d; // COMPILE TIME ERROR if you do this
if (typeof response === "string") {
console.log(response.toUpperCase()); // `response` now has type 'string'

TODO: blame this change. Don't know what this shouldve done

You can also assert a type, or use a type guard against an unknown type. This is better than resorting to any.

  1. Project References

Project references allow TypeScript projects to depend on other TypeScript projects – specifically, allowing tsconfig.json files to reference other tsconfig.json files. This lets large codebases scale without recompiling every part of the codebase every time, by breaking it up into multiple projects.

In each folder, create a tsconfig.json that includes at least:

"compilerOptions": {
"composite": true, // tells TSC it is a subproject of a larger project
"declaration": true, // emit .d.ts declaration files since project references dont have access to source ts files. important for project references to work!
"declarationMap": true, // sourcemaps for .d.ts
"rootDir": "." // specify compile it relative to root project at .
"include": ["./**/*.ts"],
"references": [
// (optional) array of subprojects your subproject depends on
"path": "../myreferencedproject", // must have tsconfig.json
"prepend": true // concatenate js and sourcemaps generated by this subproject, if and only if using outFile

and the root tsconfig.json that references top level subproject:

"files": [],
"references": [{ "path": "./proj1" }, { "path": "./proj2" }]

and you must run tsc --build or tsc -b.

To save the tsconfig boilerplate, you can use the extends option:

"extends": "../tsconfig.base"
// more stuff here

TypeScript 3.1

[Release Notes | Blog Post]

  1. Properties declarations on functions

Attaching properties to functions like this "just works" now:

export const FooComponent = ({ name }) => <div>Hello! I am {name}</div>;
FooComponent.defaultProps = {
name: "swyx",

TypeScript 3.2

[Release Notes | Blog Post]

nothing specifically React related.

TypeScript 3.3

[Release Notes | Blog Post]

nothing specifically React related.

TypeScript 3.4

[Release Notes | Blog Post]

  1. const assertions
export function useLoading() {
const [isLoading, setState] = React.useState(false);
const load = (aPromise: Promise<any>) => {
return aPromise.finally(() => setState(false));
return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[]

More info on places you can use const assertions.

TypeScript 3.5

[Release Notes | Blog Post]

  1. Built-in <Omit> Type!!

  2. Higher order type inference from generic constructors

type ComponentClass<P> = new (props: P) => Component<P>;
declare class Component<P> {
props: P;
constructor(props: P);
declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>;
type NestedProps<T> = { foo: number; stuff: T };
declare class GenericComponent<T> extends Component<NestedProps<T>> {}
// type is 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>'
const GenericComponent2 = myHoc(GenericComponent);

See also Notes from Google upgrading to 3.5

TypeScript 3.6

[Release Notes | Blog Post]

Nothing particularly React specific but the playground got an upgrade and Ambient Classes and Functions Can Merge

TypeScript 3.7

[Release Notes | Blog Post]

  1. Optional Chaining
let x = foo?.bar.baz();
// is equivalent to
let x = foo === null || foo === undefined ? undefined :;
// Optional Element access
function tryGetFirstElement<T>(arr?: T[]) {
return arr?.[0];
// Optional Call
async function makeRequest(url: string, log?: (msg: string) => void) {
log?.(`Request started at ${new Date().toISOString()}`);
const result = (await fetch(url)).json();
log?.(`Request finished at at ${new Date().toISOString()}`);
return result;
  1. Nullish Coalescing
let x = foo ?? bar();
// equivalent to
let x = foo !== null && foo !== undefined ? foo : bar();

YOU SHOULD USUALLY USE ?? WHEREVER YOU NORMALLY USE || unless you truly mean falsiness:

function ShowNumber({ value }: { value: number }) {
let _value = value || 0.5; // will replace 0 with 0.5 even if user really means 0
// etc...
  1. Assertion Functions
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg);
function yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?

You can also assert without a custom function:

function assertIsString(val: any): asserts val is string {
if (typeof val !== "string") {
throw new AssertionError("Not a string!");
function yell(str: any) {
// Now TypeScript knows that 'str' is a 'string'.
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
  1. ts-nocheck

You can now add // @ts-nocheck to the top of TypeScript files! good for migrations.

TypeScript 3.8

[Release Notes | Blog Post]

  1. Type-Only Imports and Exports
import type { SomeThing } from "./some-module.js";
export type { SomeThing };
  1. ECMAScript Private Fields

Not really React specific but ok Bloomberg

  1. export * as ns Syntax

This is ES2020 syntax. Instead of

import * as utilities from "./utilities.js";
export { utilities };

you can do

export * as utilities from "./utilities.js";
  1. Top-Level await

not React specific but gj Myles

  1. JSDoc Property Modifiers

handy for JSDoc users - @public, @private, @protected, @readonly

  1. Better Directory Watching on Linux and watchOptions
  2. “Fast and Loose” Incremental Checking

assumeChangesOnlyAffectDirectDependencies reduces build times for extremely large codebases.

TypeScript 3.9

[Release Notes | Blog Post]

  1. (minor feature) New ts-expect-error directive.

Use this when writing tests you expect to error.

// @ts-expect-error
console.log(47 * "octopus");

Pick ts-expect-error if:

  • you’re writing test code where you actually want the type system to error on an operation
  • you expect a fix to be coming in fairly quickly and you just need a quick workaround
  • you’re in a reasonably-sized project with a proactive team that wants to remove suppression comments as soon affected code is valid again

Pick ts-ignore if:

  • you have an a larger project and and new errors have appeared in code with no clear owner
  • you are in the middle of an upgrade between two different versions of TypeScript, and a line of code errors in one version but not another.
  • you honestly don’t have the time to decide which of these options is better.
  1. } and > are Now Invalid JSX Text Characters

They were always invalid, but now TypeScript and Babel are enforcing it:

Unexpected token. Did you mean `{'>'}` or `&gt;`?
Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

You can convert these in bulk if needed.

TypeScript 4.0

[Release Notes | Blog Post]

It's for custom pragmas with Preact

// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "preact";
let stuff = (
// transformed to
let stuff = h(Fragment, null, h("div", null, "Hello"));

Possibly in 4.1

TypeScript Roadmap and Spec

Did you also know you can read the TypeScript spec online??