React + TypeScript - The Basics

Why?

Using TypeScript with React helps implement better type checking in the application, which eventually results in catching errors early, getting better IntelliSense in the code editor, and achieving better code readability and maintainability.

Setup

Create a new react app that uses the TypeScript template by running the following command. Replace <app_name> with the name of your application.

npx create-react-app <app_name> --template typescript

Using Props

Create a TypeScript type or an interface to define props that the component expects to receive.

interface CarProps { 
   model: string; 
   color: string; 
};

Declare a React functional component of the type React.FC that expects the above props as follows:-

const Car : React.FC<CarProps> = ({model, color, children}): JSX.Element => { 
   return <div>...</div>
};

Notice in the above example we did not explicitly mention anything about children property in the prop interface but are still able to access it within the component. Annotating with React.FC helps TypeScript to infer that Car is a React (functional) component and hence will make component-level properties like propTypes, displayName, children etc. available to it implicitly.

React class component can be declared as follows, where CarProps is an interface defined earlier?

class Car extends React.Component<CarProps> { 
   ...
};

Using State

TypeScript can infer the type implicitly for simple or primitive types. In the example below, TypeScript infers that counter is a number.

const [counter, setCounter] = useState(0);

When using complex or non-primitive types, along with explicit type definition, we may have to factor in the possibility of the state variable being undefined or null like in the example below.

const [user, setUser] = useState<{name: string, age: number} | null>(null);

In the above example, user is an object that contains name and age properties and is initialized to null. Alternatively, we can define a separate type or interface for user like so:-

interface User { 
   name: string; 
   age: number; 
}; 

const App: React.FC = (): JSX.Element => { 
   const [user, setUser] = useState<User | null>(null); 
};

For React class components, state variables can be annotated while they are declared. Alternatively, we can have a separate type or interface definition for the state variables and use it along with the prop interface while creating the component.

interface CarProps { 
   model: string; 
   color: string; 
}; 

interface CarState { 
   loading: false; 
}; 

class Car extends React.Component<CarProps, CarState> { 
   ...
}

Using Refs

Refs can be annotated depending on the type of HTML element it refers to. For example, a ref attached to an input element can be annotated as follows:-

const inputRef = useRef<HTMLInputElement | null>(null);

Using Event Handlers

Type inference is applied when event handlers are declared inline, but not for standalone event handlers. Annotation for the event handlers depends on the type of event being handled and the type of element that triggers the event. For example, a change event on an input element can be defined as follows:-

const onInputChange = (event: React.ChangeEvent<HTMLInputElement>)=> {
 ...
};

References

https://create-react-app.dev/docs/adding-typescript

https://github.com/typescript-cheatsheets/react#reacttypescript-cheatsheets