Skip to main content

TypeScript: React

Resources

A great resource on TypeScript in React is available here:

📘 react.dev > typescript

a useful cheatsheet is available here:

📘 React TypeScript Cheatsheet

a very useful sandbox is available here:

🔗 TypeScript Playground

props

Let's start with a simple example of a functional component that takes a prop. First we will look at the standard JavaScript version, and then we will modify it to use TypeScript.

MyButton.tsx
function MyButton({ title }) {
return <button>{title}</button>;
}

In a .tsx component we must define the type of the prop.

MyButton.tsx
function MyButton({ title }: { title: string }) {
return <button>{title}</button>;
}

or we could use an interface.

MyButton.tsx
interface MyButtonProps {
title: string;
}

function MyButton({ title }: MyButtonProps) {
return <button>{title}</button>;
}

Hooks

React includes a set of built in type definitions for your hooks. However there are times where we will want to explicitly define the type of a hook, or we will need to import the type from a library that we are using.

useState

Basic Usage

The useState Hook will re-use the value passed in as the initial state to determine what the type of the value should be. For example:

// Infer the type as "boolean"
const [enabled, setEnabled] = useState(false);

we can also explicitly define the type.

// Explicitly set the type to "boolean"
const [enabled, setEnabled] = useState<boolean>(false);

Union Types

or a more useful instance of this would be defining a union type for the state to set explicit options:

type Status = "idle" | "loading" | "success" | "error";

const [status, setStatus] = useState<Status>("idle");

Or, as recommended in Principles for structuring state, you can group related state as an object and describe the different possibilities via object types:

type RequestState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: any }
| { status: "error"; error: Error };

const [requestState, setRequestState] = useState<RequestState>({
status: "idle",
});

Imported State Types

If you are using a library like React-Table, a common pattern is to import the state type from the library:

import {
PaginationState,
ColumnFiltersState,
//...
} from "@tanstack/react-table";

const DataTable: React.FC<DataTableProps> = ({ data, columns }) => {
const [pagination, setPagination] = React.useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
//...
};

export default DataTable;

useCallback

When using useCallback in a React component with TypeScript, you need to ensure that the callback function and its dependencies are correctly typed. Here are the key points and examples to consider:

Basic Usage

import React, { useState, useCallback } from "react";

const Counter: React.FC = () => {
const [count, setCount] = useState(0);

const increment = useCallback(() => {
setCount(count + 1);
}, [count]); // The dependency array includes count

return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};

export default Counter;

Typing the Callback Function

When the callback function takes arguments, you should explicitly type the arguments and the return type if needed:

import React, { useState, useCallback } from "react";

const Multiply: React.FC = () => {
const [result, setResult] = useState(1);

const multiply = useCallback((factor: number): void => {
setResult((prevResult) => prevResult * factor);
}, []);

return (
<div>
<p>{result}</p>
<button onClick={() => multiply(2)}>Multiply by 2</button>
</div>
);
};

export default Multiply;

Complex Types

When dealing with complex types, such as objects, you should define an interface for the types:

import React, { useState, useCallback } from "react";

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

const UserList = () => {
const [users, setUsers] = useState<User[]>([]);

const addUser = useCallback((newUser: User): void => {
setUsers((prevUsers) => [...prevUsers, newUser]);
}, []);

return (
<div>
<button
onClick={() => addUser({ id: users.length + 1, name: "New User" })}
>
Add User
</button>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};

export default UserList;

React Built in Types

There is quite an expansive set of types which come from the @types/react package, it is worth a read when you feel comfortable with how React and TypeScript interact. You can find them in React’s folder in DefinitelyTyped.

Here are some of the common ones.

DOM Events

When working with DOM events in React, the type of the event can often be inferred from the event handler. However, when you want to extract a function to be passed to an event handler, you will need to explicitly set the type of the event.

import { useState } from "react";

export default function Form() {
const [value, setValue] = useState("Change me");

function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
setValue(event.currentTarget.value);
}

return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}

There are many many of these pre-defined event types, you can find them in the React’s folder in DefinitelyTyped

Children

There are two common paths to describing the children of a component. The first is to use the React.ReactNode type, which is a union of all the possible types that can be passed as children in JSX:

interface ModalRendererProps {
title: string;
children: React.ReactNode;
}

This is a very broad definition of children. The second is to use the React.ReactElement type, which is only JSX elements and not JavaScript primitives like strings or numbers:

interface ModalRendererProps {
title: string;
children: React.ReactElement;
}

Note, that you cannot use TypeScript to describe that the children are a certain type of JSX elements, so you cannot use the type-system to describe a component which only accepts <li> children.

Refs

When working with refs in React, you can use the React.RefObject type to describe the type of the ref:

import { useRef } from "react";

export const MyComponent = () => {
const inputRef = useRef<HTMLInputElement>(null);

const handleClick = () => {
inputRef.current?.focus();
};

return (
<div>
<input type="text" ref={inputRef} />
<button onClick={handleClick}>Focus Input</button>
</div>
);
};

Style props

When using inline styles in React, you can use React.CSSProperties to describe the object passed to the style prop. This type is a union of all the possible CSS properties, and is a good way to ensure you are passing valid CSS properties to the style prop, and to get auto-complete in your editor.

interface MyComponentProps {
style: React.CSSProperties;
}

Recent Work

Free desktop AI Chat client, designed for developers and businesses. Unlocks advanced model settings only available in the API. Includes quality of life features like custom syntax highlighting.

Learn More
slide-6
slide-5
slide-2
slide-1
slide-3
slide-4
Technologies Used
TypeScript
Electron
React

BidBear

bidbear.io

Bidbear is a report automation tool. It downloads Amazon Seller and Advertising reports, daily, to a private database. It then merges and formats the data into beautiful, on demand, exportable performance reports.

Learn More
slide-1
slide-2
slide-5
slide-3
slide-4

Technologies Used

Front End
JavaScript
Docker
React
Redux
Vite
Next
Docusaurus
Stripe
Sentry
D3
React-Flow
TipTap
Back End
JavaScript
Python
AWS CognitoCognito
AWS API GatewayAPI Gateway
AWS LambdaLambda
AWS AthenaAthena
AWS GlueGlue
AWS Step FunctionsStep Functions
AWS SQSSQS
AWS DynamoDBDynamo DB
AWS S3S3
AWS CloudwatchCloudWatch
AWS CloudFrontCloudFront
AWS Route 53Route 53
AWS EventBridgeEventBridge