TypeScript: React
Resourcesβ
A great resource on TypeScript in React is available here:
a useful cheatsheet is available here:
π React TypeScript Cheatsheet
a very useful sandbox is available here:
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.
function MyButton({ title }) {
return <button>{title}</button>;
}
In a .tsx
component we must define the type of the prop.
function MyButton({ title }: { title: string }) {
return <button>{title}</button>;
}
or we could use an interface.
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;
}
Comments
Recent Work
Basalt
basalt.softwareFree 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.
BidBear
bidbear.ioBidbear 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.