Hooks

Hooks are supported in @types/react from v16.8 up.

useState

Type inference works very well most of the time:

const [val, toggle] = React.useState(false); // `val` is inferred to be a boolean, `toggle` only takes booleans

See also the Using Inferred Types section if you need to use a complex type that you've relied on inference for.

However, many hooks are initialized with null-ish default values, and you may wonder how to provide types. Explicitly declare the type, and use a union type:

const [user, setUser] = React.useState<IUser | null>(null);
// later...
setUser(newUser);

useReducer

You can use Discriminated Unions for reducer actions. Don't forget to define the return type of reducer, otherwise TypeScript will infer it.

const initialState = { count: 0 };
type ACTIONTYPE =
| { type: "increment"; payload: number }
| { type: "decrement"; payload: string };
function reducer(state: typeof initialState, action: ACTIONTYPE) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - Number(action.payload) };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement", payload: "5" })}>
-
</button>
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
</>
);
}

View in the TypeScript Playground

Usage with `Reducer` from `redux`

In case you use the redux library to write reducer function, It provides a convenient helper of the format Reducer<State, Action> which takes care of the return type for you.

So the above reducer example becomes:

import { Reducer } from 'redux';
export function reducer: Reducer<AppState, Action>() {}

useEffect

When using useEffect, take care not to return anything other than a function or undefined, otherwise both TypeScript and React will yell at you. This can be subtle when using arrow functions:

function DelayedEffect(props: { timerMs: number }) {
const { timerMs } = props;
// bad! setTimeout implicitly returns a number because the arrow function body isn't wrapped in curly braces
useEffect(
() =>
setTimeout(() => {
/* do stuff */
}, timerMs),
[timerMs]
);
return null;
}

useRef

When using useRef, you have two options when creating a ref container that does not have an initial value:

const ref1 = useRef<HTMLElement>(null!);
const ref2 = useRef<HTMLElement | null>(null);

The first option will make ref1.current read-only, and is intended to be passed in to built-in ref attributes that React will manage (because React handles setting the current value for you).

What is the ! at the end of null!?

null! is a non-null assertion operator (the !). It asserts that any expression before it is not null or undefined, so if you have useRef<HTMLElement>(null!) it means that you're instantiating the ref with a current value of null but lying to TypeScript that it's not null.

function MyComponent() {
const ref1 = useRef<HTMLElement>(null!);
useEffect(() => {
doSomethingWith(ref1.current); // TypeScript won't require null-check e.g. ref1 && ref1.current
});
return <div ref={ref1}> etc </div>;
}

The second option will make ref2.current mutable, and is intended for "instance variables" that you manage yourself.

function TextInputWithFocusButton() {
// initialise with null, but tell TypeScript we are looking for an HTMLInputElement
const inputEl = React.useRef<HTMLInputElement>(null);
const onButtonClick = () => {
// strict null checks need us to check if inputEl and current exist.
// but once current exists, it is of type HTMLInputElement, thus it
// has the method focus! โœ…
if (inputEl && inputEl.current) {
inputEl.current.focus();
}
};
return (
<>
{/* in addition, inputEl only can be used with input elements. Yay! */}
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

View in the TypeScript Playground

example from Stefan Baumgartner

useImperativeHandle

we dont have much here, but this is from a discussion in our issues

type ListProps<ItemType> = {
items: ItemType[];
innerRef?: React.Ref<{ scrollToItem(item: ItemType): void }>;
};
function List<ItemType>(props: ListProps<ItemType>) {
useImperativeHandle(props.innerRef, () => ({
scrollToItem() {},
}));
return null;
}

Custom Hooks

If you are returning an array in your Custom Hook, you will want to avoid type inference as TypeScript will infer a union type (when you actually want different types in each position of the array). Instead, use TS 3.4 const assertions:

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

View in the TypeScript Playground

This way, when you destructure you actually get the right types based on destructure position.

Alternative: Asserting a tuple return type

If you are having trouble with const assertions, you can also assert or define the function return types:

export function useLoading() {
const [isLoading, setState] = React.useState(false);
const load = (aPromise: Promise<any>) => {
setState(true);
return aPromise.finally(() => setState(false));
};
return [isLoading, load] as [
boolean,
(aPromise: Promise<any>) => Promise<any>
];
}

A helper function that automatically types tuples can also be helpful if you write a lot of custom hooks:

function tuplify<T extends any[]>(...elements: T) {
return elements;
}
function useArray() {
const numberValue = useRef(3).current;
const functionValue = useRef(() => {}).current;
return [numberValue, functionValue]; // type is (number | (() => void))[]
}
function useTuple() {
const numberValue = useRef(3).current;
const functionValue = useRef(() => {}).current;
return tuplify(numberValue, functionValue); // type is [number, () => void]
}

Note that the React team recommends that custom hooks that return more than two values should use proper objects instead of tuples, however.

More Hooks + TypeScript reading:

If you are writing a React Hooks library, don't forget that you should also expose your types for users to use.

Example React Hooks + TypeScript Libraries:

Something to add? File an issue.