Typing Hooks (useState, useEffect)
In this page:
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
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
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
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
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
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: