← Back to TypeScript Course | Chapter 14: TypeScript with React | Lesson 5 of 8

Typing Hooks (useState, useEffect)

React hooks work well with TypeScript because their generic types can describe state and other values precisely. useState often infers types automatically, while useEffect can use typed functions and dependencies.

Typing useState

Typing useState follows the same inference-or-explicit-generic pattern as anywhere else — TypeScript infers from the initial value, or you supply useState<T>() explicitly when the initial value alone can't capture the full intended type.

Example: Typing useState

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

function Toggle(): React.JSX.Element {
	const [on, setOn] = useState(false);
	return <p>{on ? "On" : "Off"}</p>;
}

export default Toggle;

Typing useEffect

useEffect's callback itself isn't typed with a generic — its return value is constrained to either void or a cleanup function, and TypeScript will flag an effect that accidentally returns something else, like a Promise.

Example: Typing useEffect

typescript
import React, { useEffect } from "react";

function Logger(): React.JSX.Element {
	useEffect(() => {
		console.log("Mounted");
	}, []);
	return <p>Logged</p>;
}

export default Logger;

Typed Data Fetching

Typed data fetching inside an effect usually means typing the fetched response against an interface right where it's parsed, so the rest of the component can rely on that shape instead of treating fetched data as any.

Example: Typed Data Fetching

typescript
import React, { useEffect, useState } from "react";

interface User {
	name: string;
}

function Profile(): React.JSX.Element {
	const [user, setUser] = useState<User | null>(null);
	useEffect(() => {
		setUser({ name: "Ravi" });
	}, []);
	return <p>{user?.name}</p>;
}

export default Profile;

Cleanup Functions

A cleanup function returned from useEffect is typed as () => void, and it's what React calls automatically when the component unmounts or before the effect re-runs — omitting one for something like a subscription is a common bug.

Example: Cleanup Functions

typescript
import React, { useEffect } from "react";

function Timer(): React.JSX.Element {
	useEffect(() => {
		const id = setInterval(() => console.log("tick"), 1000);
		return () => clearInterval(id);
	}, []);
	return <p>Timer running</p>;
}

export default Timer;

Separating Typed Effects

Separating typed effects into distinct useEffect calls (each with its own tightly-scoped dependency array) is easier to type correctly than one large effect handling several unrelated concerns and dependencies at once.

Example: Separating Typed Effects

typescript
import React, { useEffect } from "react";

function Widget({ id }: { id: number }): React.JSX.Element {
	useEffect(() => {
		console.log("id changed", id);
	}, [id]);
	useEffect(() => {
		console.log("mounted once");
	}, []);
	return <p>Widget {id}</p>;
}

export default Widget;
🔒

Chapter Quiz — Complete all 8 topics to unlock

0/8 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.