React Intermediate

Advanced Hooks: useEffect and Custom Hooks

CodingerWeb
CodingerWeb
20 views 45 min read

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