The useEffect Hook is used in React to perform side effects such as fetching data, working with timers, subscriptions, event listeners, and other operations that happen outside the normal rendering process.
useEffect. However, explicitly typing API data,
timer references, event handlers, and cleanup-related values can
make React applications safer and easier to maintain.
useEffect is a React Hook used to perform
side effects in a functional component.
import { useEffect } from "react";
useEffect(() => {
console.log("Effect executed");
});
The function inside useEffect is called the
effect function.
Import useEffect from React before using it.
import { useEffect } from "react";
It can be imported together with other React Hooks.
import { useEffect, useState } from "react";
useEffect(() => {
// effect code
});
The callback function contains the code that should run as a side effect.
useEffect(() => {
console.log("Component loaded");
}, []);
An empty dependency array tells React that the effect does not depend on changing values.
const [count, setCount] =
useState<number>(0);
useEffect(() => {
console.log(count);
}, [count]);
The effect runs when the count value changes.
The second argument of useEffect is called the
dependency array.
useEffect(() => {
console.log("Effect");
}, [count]);
React uses the dependencies to determine when the effect should run again.
useEffect(() => {
console.log(name);
console.log(age);
}, [name, age]);
Multiple values can be included in the dependency array.
const [name, setName] =
useState<string>("");
useEffect(() => {
console.log(
"Name:",
name
);
}, [name]);
TypeScript already knows that name is a
string because of the state definition.
One common use of useEffect is loading data
when a component starts.
useEffect(() => {
fetch("/api/students")
.then(response => response.json())
.then(data => {
console.log(data);
});
}, []);
interface Student {
id: number;
name: string;
course: string;
}
const [students, setStudents] =
useState<Student[]>([]);
The state is now prepared to store an array of typed student objects.
interface Student {
id: number;
name: string;
}
const [students, setStudents] =
useState<Student[]>([]);
useEffect(() => {
fetch("/api/students")
.then(response =>
response.json()
)
.then((data: Student[]) => {
setStudents(data);
});
}, []);
const [loading, setLoading] =
useState<boolean>(true);
useEffect(() => {
fetch("/api/students")
.then(response =>
response.json()
)
.then(data => {
setStudents(data);
setLoading(false);
});
}, []);
A boolean state can be used to represent whether data is currently loading.
const [error, setError] =
useState<string | null>(null);
A union type allows the error state to contain either a
string or null.
useEffect(() => {
fetch("/api/students")
.then(response => {
if (!response.ok) {
throw new Error(
"Failed to load students"
);
}
return response.json();
})
.then(data => {
setStudents(data);
})
.catch(error => {
setError(error.message);
});
}, []);
An effect can return a cleanup function.
useEffect(() => {
console.log("Started");
return () => {
console.log("Cleaned up");
};
}, []);
Cleanup is useful for removing subscriptions, timers, and event listeners.
In browser-based React applications, a timeout can be
represented using ReturnType<typeof setTimeout>.
useEffect(() => {
const timer: ReturnType<typeof setTimeout> =
setTimeout(() => {
console.log("Finished");
}, 1000);
return () => {
clearTimeout(timer);
};
}, []);
useEffect(() => {
const interval: ReturnType<typeof setInterval> =
setInterval(() => {
console.log("Running...");
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
The cleanup function prevents the interval from continuing after it is no longer needed.
useEffect(() => {
const handleResize = () => {
console.log(window.innerWidth);
};
window.addEventListener(
"resize",
handleResize
);
return () => {
window.removeEventListener(
"resize",
handleResize
);
};
}, []);
Cleanup prevents resources from continuing to run after a component no longer needs them.
interface UserProps {
userId: number;
}
function User({
userId
}: UserProps) {
useEffect(() => {
console.log(
"User ID:",
userId
);
}, [userId]);
return <h2>User {userId}</h2>;
}
The effect runs again when the typed
userId prop changes.
The effect callback itself should not normally be declared
directly as async. Instead, define an async
function inside the effect.
useEffect(() => {
const loadStudents = async () => {
const response =
await fetch("/api/students");
const data: Student[] =
await response.json();
setStudents(data);
};
loadStudents();
}, []);
useEffect(() => {
const loadStudents = async () => {
try {
const response =
await fetch("/api/students");
if (!response.ok) {
throw new Error(
"Unable to load students"
);
}
const data: Student[] =
await response.json();
setStudents(data);
} catch (error) {
if (error instanceof Error) {
setError(error.message);
} else {
setError("Unknown error");
}
}
};
loadStudents();
}, []);
The same Hook can be used in React Native for tasks such as loading data or responding to state changes.
import {
useEffect,
useState
} from "react";
import {
Text,
View
} from "react-native";
function App() {
const [message, setMessage] =
useState<string>("Loading...");
useEffect(() => {
setMessage("Welcome to React Native");
}, []);
return (
<View>
<Text>
{message}
</Text>
</View>
);
}
interface Student {
id: number;
name: string;
}
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState<boolean>(true);
const [error, setError] =
useState<string | null>(null);
useEffect(() => {
const loadData = async () => {
try {
setLoading(true);
const response =
await fetch("/api/students");
if (!response.ok) {
throw new Error(
"Request failed"
);
}
const data: Student[] =
await response.json();
setStudents(data);
} catch (error) {
if (error instanceof Error) {
setError(error.message);
}
} finally {
setLoading(false);
}
};
loadData();
}, []);
interface Student {
id: number;
name: string;
}
interface Props {
studentId: number;
}
function StudentDetails({
studentId
}: Props) {
const [student, setStudent] =
useState<Student | null>(null);
useEffect(() => {
const loadStudent = async () => {
const response =
await fetch(
`/api/students/${studentId}`
);
const data: Student =
await response.json();
setStudent(data);
};
loadStudent();
}, [studentId]);
return (
<div>
{student && (
<h2>
{student.name}
</h2>
)}
</div>
);
}
When studentId changes, the effect runs again
and loads the corresponding student.
async.Values used by an effect that come from component scope should generally be considered when defining the dependency array.
useEffect(() => {
console.log(userId);
}, [userId]);
Keeping dependencies accurate helps the effect respond to changes correctly.
interface Product {
id: number;
name: string;
price: number;
}
const [products, setProducts] =
useState<Product[]>([]);
const [loading, setLoading] =
useState<boolean>(false);
const [error, setError] =
useState<string | null>(null);
Separating these states makes API-driven components easier to understand and maintain.
string | null for nullable error states.import {
useEffect,
useState
} from "react";
interface Student {
id: number;
name: string;
course: string;
}
function StudentList() {
const [students, setStudents] =
useState<Student[]>([]);
const [loading, setLoading] =
useState<boolean>(true);
const [error, setError] =
useState<string | null>(null);
useEffect(() => {
const loadStudents = async () => {
try {
setLoading(true);
setError(null);
const response =
await fetch(
"/api/students"
);
if (!response.ok) {
throw new Error(
"Failed to load students"
);
}
const data: Student[] =
await response.json();
setStudents(data);
} catch (error) {
if (error instanceof Error) {
setError(error.message);
} else {
setError(
"Something went wrong"
);
}
} finally {
setLoading(false);
}
};
loadStudents();
}, []);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error}</p>;
}
return (
<div>
<h2>
Student List
</h2>
{students.map(student => (
<div key={student.id}>
<h3>
{student.name}
</h3>
<p>
Course: {student.course}
</p>
</div>
))}
</div>
);
}
export default StudentList;
This complete example combines TypeScript interfaces,
typed state, useEffect, asynchronous API
loading, loading state, error state, and typed student
data.
useEffect is used for side effects in React.string | null for nullable error states.Question: Which Hook is used in React to perform side effects?