Table of Contents
Introduction to Advanced Hooks
In this lesson, we'll explore more advanced React hooks including useEffect for side effects and creating custom hooks for reusable logic.
Understanding useEffect
The useEffect hook lets you perform side effects in function components. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
Basic useEffect Usage
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// This runs after every render
fetchUser(userId)
.then(userData => {
setUser(userData);
setLoading(false);
});
}, [userId]); // Dependency array
if (loading) return Loading...;
return (
{user.name}
{user.email}
);
}
useEffect with Cleanup
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
// Cleanup function
return () => {
clearInterval(timer);
};
}, []); // Empty dependency array means this runs once
Creating Custom Hooks
Custom hooks are JavaScript functions that start with "use" and can call other hooks. They let you extract component logic into reusable functions.
Custom Hook Example: useCounter
// hooks/useCounter.js
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
export default useCounter;
Using the Custom Hook
import React from 'react';
import useCounter from './hooks/useCounter';
function CounterComponent() {
const { count, increment, decrement, reset } = useCounter(10);
return (
Count: {count}
);
}
Custom Hook: useFetch
// hooks/useFetch.js
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Practical Exercise
Create a todo app that uses:
- useEffect to load todos from localStorage
- A custom hook for managing todo state
- Proper cleanup for any subscriptions
Solution: useTodos Custom Hook
// hooks/useTodos.js
import { useState, useEffect } from 'react';
function useTodos() {
const [todos, setTodos] = useState([]);
useEffect(() => {
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
setTodos(JSON.parse(savedTodos));
}
}, []);
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
const addTodo = (text) => {
const newTodo = {
id: Date.now(),
text,
completed: false
};
setTodos([...todos, newTodo]);
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return { todos, addTodo, toggleTodo, deleteTodo };
}
export default useTodos;
Best Practices
- Always include dependencies in useEffect dependency array
- Use custom hooks to extract and reuse stateful logic
- Clean up subscriptions and timers in useEffect
- Keep custom hooks focused on a single concern