🎉 Refer & Earn

Earn Rs.1500 for every friend you refer!
Enter your phone number and we'll contact you with details

📞
🔒Your number is safe with us
    Back to Blogs
    5 Essential React Hooks Every Developer Should Master

    5 Essential React Hooks Every Developer Should Master

    Master these 5 React hooks to write cleaner, more efficient React code. From useState to custom hooks, learn practical examples and best practices.

    FYP Maker Team

    5 Essential React Hooks Every Developer Should Master

    React Hooks revolutionized how we write React components, making functional components as powerful as class components while keeping the code cleaner and more reusable. In this post, we'll dive deep into 5 essential hooks that every React developer should master.

    1. useState - Managing Component State

    The useState hook is your go-to solution for managing local component state in functional components.

    Basic Usage

    import React, { useState } from "react";
    
    function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>Increment</button>
          <button onClick={() => setCount(count - 1)}>Decrement</button>
        </div>
      );
    }

    Advanced Patterns

    Functional Updates: When the new state depends on the previous state, use functional updates to avoid stale closures:

    function Counter() {
      const [count, setCount] = useState(0);
    
      const increment = () => {
        setCount((prevCount) => prevCount + 1);
      };
    
      const incrementTwice = () => {
        setCount((prevCount) => prevCount + 1);
        setCount((prevCount) => prevCount + 1);
      };
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={increment}>+1</button>
          <button onClick={incrementTwice}>+2</button>
        </div>
      );
    }

    Complex State Objects: For complex state, consider using multiple useState calls or useReducer:

    function UserProfile() {
      const [user, setUser] = useState({
        name: "",
        email: "",
        age: 0,
      });
    
      const updateUser = (field, value) => {
        setUser((prevUser) => ({
          ...prevUser,
          [field]: value,
        }));
      };
    
      return (
        <form>
          <input
            value={user.name}
            onChange={(e) => updateUser("name", e.target.value)}
            placeholder="Name"
          />
          <input
            value={user.email}
            onChange={(e) => updateUser("email", e.target.value)}
            placeholder="Email"
          />
        </form>
      );
    }

    2. useEffect - Side Effects and Lifecycle

    The useEffect hook handles side effects in functional components, replacing lifecycle methods from class components.

    Basic Side Effects

    import React, { useState, useEffect } from "react";
    
    function UserList() {
      const [users, setUsers] = useState([]);
      const [loading, setLoading] = useState(true);
    
      useEffect(() => {
        const fetchUsers = async () => {
          try {
            const response = await fetch("/api/users");
            const userData = await response.json();
            setUsers(userData);
          } catch (error) {
            console.error("Failed to fetch users:", error);
          } finally {
            setLoading(false);
          }
        };
    
        fetchUsers();
      }, []); // Empty dependency array means this runs once on mount
    
      if (loading) return <div>Loading...</div>;
    
      return (
        <ul>
          {users.map((user) => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      );
    }

    Cleanup and Dependencies

    function Timer() {
      const [seconds, setSeconds] = useState(0);
    
      useEffect(() => {
        const interval = setInterval(() => {
          setSeconds((prevSeconds) => prevSeconds + 1);
        }, 1000);
    
        // Cleanup function
        return () => clearInterval(interval);
      }, []); // Empty deps = run once, cleanup on unmount
    
      return <div>Timer: {seconds} seconds</div>;
    }

    Conditional Effects

    function SearchResults({ query }) {
      const [results, setResults] = useState([]);
    
      useEffect(() => {
        if (!query) {
          setResults([]);
          return;
        }
    
        const searchAPI = async () => {
          const response = await fetch(`/api/search?q=${query}`);
          const data = await response.json();
          setResults(data);
        };
    
        searchAPI();
      }, [query]); // Re-run when query changes
    
      return (
        <div>
          {results.map((result) => (
            <div key={result.id}>{result.title}</div>
          ))}
        </div>
      );
    }

    3. useMemo - Performance Optimization

    The useMemo hook memoizes expensive calculations, preventing unnecessary re-computations.

    Basic Usage

    import React, { useState, useMemo } from "react";
    
    function ExpensiveCalculation({ items }) {
      const [multiplier, setMultiplier] = useState(1);
    
      // Expensive calculation that we want to memoize
      const expensiveValue = useMemo(() => {
        console.log("Calculating expensive value...");
        return items.reduce((sum, item) => sum + item.value, 0) * multiplier;
      }, [items, multiplier]); // Only recalculate when items or multiplier change
    
      return (
        <div>
          <p>Expensive Value: {expensiveValue}</p>
          <button onClick={() => setMultiplier((m) => m + 1)}>
            Increase Multiplier
          </button>
        </div>
      );
    }

    Complex Object Memoization

    function UserDashboard({ users, filters }) {
      const filteredAndSortedUsers = useMemo(() => {
        return users
          .filter((user) => {
            return filters.every((filter) => filter.fn(user));
          })
          .sort((a, b) => a.name.localeCompare(b.name));
      }, [users, filters]);
    
      return (
        <div>
          {filteredAndSortedUsers.map((user) => (
            <UserCard key={user.id} user={user} />
          ))}
        </div>
      );
    }

    4. useCallback - Function Memoization

    The useCallback hook memoizes functions, preventing unnecessary re-renders of child components.

    Preventing Unnecessary Re-renders

    import React, { useState, useCallback } from "react";
    
    // Child component that might re-render unnecessarily
    const Button = React.memo(({ onClick, children }) => {
      console.log("Button rendered");
      return <button onClick={onClick}>{children}</button>;
    });
    
    function ParentComponent() {
      const [count, setCount] = useState(0);
      const [otherValue, setOtherValue] = useState(0);
    
      // Without useCallback, this function is recreated on every render
      const handleClick = useCallback(() => {
        setCount((prevCount) => prevCount + 1);
      }, []); // No dependencies, so function never changes
    
      const handleOtherClick = useCallback(() => {
        setOtherValue((prev) => prev + 1);
      }, []);
    
      return (
        <div>
          <p>Count: {count}</p>
          <p>Other Value: {otherValue}</p>
          <Button onClick={handleClick}>Increment Count</Button>
          <Button onClick={handleOtherClick}>Increment Other</Button>
        </div>
      );
    }

    Event Handlers with Dependencies

    function TodoList({ todos, onUpdateTodo }) {
      const [filter, setFilter] = useState("all");
    
      const handleToggle = useCallback(
        (id) => {
          onUpdateTodo(id, {
            completed: !todos.find((t) => t.id === id)?.completed,
          });
        },
        [todos, onUpdateTodo]
      );
    
      const filteredTodos = useMemo(() => {
        return todos.filter((todo) => {
          if (filter === "completed") return todo.completed;
          if (filter === "active") return !todo.completed;
          return true;
        });
      }, [todos, filter]);
    
      return (
        <div>
          <select value={filter} onChange={(e) => setFilter(e.target.value)}>
            <option value="all">All</option>
            <option value="active">Active</option>
            <option value="completed">Completed</option>
          </select>
    
          {filteredTodos.map((todo) => (
            <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
          ))}
        </div>
      );
    }

    5. Custom Hooks - Reusable Logic

    Custom hooks allow you to extract and reuse stateful logic between components.

    useLocalStorage Hook

    import { useState, useEffect } from "react";
    
    function useLocalStorage(key, initialValue) {
      // Get value from localStorage or use initial value
      const [storedValue, setStoredValue] = useState(() => {
        try {
          const item = window.localStorage.getItem(key);
          return item ? JSON.parse(item) : initialValue;
        } catch (error) {
          console.error(error);
          return initialValue;
        }
      });
    
      // Update localStorage when state changes
      const setValue = (value) => {
        try {
          setStoredValue(value);
          window.localStorage.setItem(key, JSON.stringify(value));
        } catch (error) {
          console.error(error);
        }
      };
    
      return [storedValue, setValue];
    }
    
    // Usage
    function Settings() {
      const [theme, setTheme] = useLocalStorage("theme", "light");
      const [language, setLanguage] = useLocalStorage("language", "en");
    
      return (
        <div>
          <select value={theme} onChange={(e) => setTheme(e.target.value)}>
            <option value="light">Light</option>
            <option value="dark">Dark</option>
          </select>
    
          <select value={language} onChange={(e) => setLanguage(e.target.value)}>
            <option value="en">English</option>
            <option value="es">Spanish</option>
          </select>
        </div>
      );
    }

    useAPI Hook

    import { useState, useEffect } from "react";
    
    function useAPI(url) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        const fetchData = async () => {
          try {
            setLoading(true);
            setError(null);
    
            const response = await fetch(url);
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
    
            const result = await response.json();
            setData(result);
          } catch (err) {
            setError(err.message);
          } finally {
            setLoading(false);
          }
        };
    
        fetchData();
      }, [url]);
    
      return { data, loading, error };
    }
    
    // Usage
    function UserProfile({ userId }) {
      const { data: user, loading, error } = useAPI(`/api/users/${userId}`);
    
      if (loading) return <div>Loading...</div>;
      if (error) return <div>Error: {error}</div>;
      if (!user) return <div>No user found</div>;
    
      return (
        <div>
          <h1>{user.name}</h1>
          <p>{user.email}</p>
        </div>
      );
    }

    useDebounce Hook

    import { useState, useEffect } from "react";
    
    function useDebounce(value, delay) {
      const [debouncedValue, setDebouncedValue] = useState(value);
    
      useEffect(() => {
        const handler = setTimeout(() => {
          setDebouncedValue(value);
        }, delay);
    
        return () => {
          clearTimeout(handler);
        };
      }, [value, delay]);
    
      return debouncedValue;
    }
    
    // Usage in a search component
    function SearchComponent() {
      const [searchTerm, setSearchTerm] = useState("");
      const debouncedSearchTerm = useDebounce(searchTerm, 500);
      const { data: results, loading } = useAPI(
        debouncedSearchTerm ? `/api/search?q=${debouncedSearchTerm}` : null
      );
    
      return (
        <div>
          <input
            type="text"
            value={searchTerm}
            onChange={(e) => setSearchTerm(e.target.value)}
            placeholder="Search..."
          />
    
          {loading && <div>Searching...</div>}
    
          {results && (
            <ul>
              {results.map((result) => (
                <li key={result.id}>{result.title}</li>
              ))}
            </ul>
          )}
        </div>
      );
    }

    Best Practices and Common Pitfalls

    1. Dependency Arrays

    Always include all dependencies in your dependency arrays to avoid bugs:

    // ❌ Missing dependency
    useEffect(() => {
      fetchData(userId);
    }, []); // userId should be in dependencies
    
    // ✅ Correct
    useEffect(() => {
      fetchData(userId);
    }, [userId]);

    2. Avoid Overusing useMemo and useCallback

    Only use them when you have actual performance issues:

    // ❌ Unnecessary memoization
    const expensiveValue = useMemo(() => {
      return a + b; // Simple calculation doesn't need memoization
    }, [a, b]);
    
    // ✅ Use for actually expensive operations
    const expensiveValue = useMemo(() => {
      return heavyCalculation(largeDataSet);
    }, [largeDataSet]);

    3. Custom Hook Naming

    Always start custom hooks with "use":

    // ❌ Wrong naming
    function localStorage(key, initialValue) {
      /* ... */
    }
    
    // ✅ Correct naming
    function useLocalStorage(key, initialValue) {
      /* ... */
    }

    Conclusion

    These 5 React hooks form the foundation of modern React development:

    1. useState - For local component state
    2. useEffect - For side effects and lifecycle events
    3. useMemo - For expensive calculations
    4. useCallback - For function memoization
    5. Custom Hooks - For reusable stateful logic

    Mastering these hooks will make you a more effective React developer, enabling you to write cleaner, more performant, and more maintainable code.

    Remember: Hooks are powerful tools, but use them judiciously. Not every piece of logic needs to be memoized, and not every effect needs optimization. Focus on readability first, then optimize when you have real performance issues.

    Happy coding! 🚀