Mastering React Hooks: The Ultimate 2024 Guide with Detailed Examples

React Hooks provide a more elegant way to manage state, handle side effects, and reuse logic in functional components. This guide walks you through all the core hooks in React, with detailed explanations and code examples, making it easy to understand their purpose and usage.


What Are React Hooks?

React Hooks are functions that let you “hook into” React’s state and lifecycle features within functional components. Before hooks, these features were only available in class components, but hooks now allow you to write cleaner and more maintainable functional components.


React Hooks

1. useState Hook

The useState hook lets you add state to functional components. It’s used when you need to keep track of data that changes over time, such as form inputs, counters, etc.

Syntax:

const [state, setState] = useState(initialState);

Example:

import React, { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>Click me</button>
        </div>
    );
}

How it Works:

  • useState takes the initial state (e.g., 0) and returns an array with two elements: the current state (count) and a function to update it (setCount).
  • Each time the state changes (e.g., when the button is clicked), the component re-renders with the new state.

2. useEffect Hook

The useEffect hook is used to handle side effects like data fetching, subscriptions, or directly interacting with the DOM. It replaces lifecycle methods such as componentDidMount, componentDidUpdate, and componentWillUnmount.

Syntax:

useEffect(() => {
    // Side effect logic
    return () => {
        // Cleanup (optional)
    };
}, [dependencies]);

Example:

import React, { useState, useEffect } from 'react';

function DataFetcher() {
    const [data, setData] = useState(null);

    useEffect(() => {
        fetch('https://jsonplaceholder.typicode.com/posts')
            .then(response => response.json())
            .then(json => setData(json));
    }, []);  // Empty dependency array means this effect runs only once (on mount)

    return (
        <div>
            {data ? data.map(post => <p key={post.id}>{post.title}</p>) : 'Loading...'}
        </div>
    );
}

How it Works:

  • useEffect accepts a function that runs after the component renders.
  • The second argument is a dependency array. If any value inside this array changes, the effect will re-run.
  • Passing an empty array ([]) ensures the effect runs only once, mimicking componentDidMount.

3. useContext Hook

useContext provides a way to access values from a context without needing to wrap your component with a Consumer. It allows you to share values like global state across multiple components.

Syntax:

const value = useContext(MyContext);

Example:

import React, { useContext } from 'react';

const UserContext = React.createContext();

function DisplayUserName() {
    const user = useContext(UserContext);
    return <div>Username: {user}</div>;
}

function App() {
    return (
        <UserContext.Provider value="JohnDoe">
            <DisplayUserName />
        </UserContext.Provider>
    );
}

How it Works:

  • useContext allows you to consume the context directly without needing the Consumer component.
  • This is a simple and effective way to manage global data like user info or theme settings.

4. useReducer Hook

The useReducer hook is used for more complex state management when useState is not sufficient. It’s an alternative to useState when you have complex state logic or the next state depends on the previous one.

Syntax:

const [state, dispatch] = useReducer(reducer, initialState);

Example:

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            return state;
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);

    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: 'increment' })}>+</button>
            <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        </div>
    );
}

How it Works:

  • useReducer takes a reducer function and an initial state.
  • The reducer function handles the logic based on the dispatched action type. This makes it useful for managing complex state logic (e.g., multiple states or state changes dependent on other actions).

5. useMemo Hook

The useMemo hook is used to memoize expensive computations, so they’re only recalculated when necessary. This is useful for optimizing performance by skipping unnecessary calculations.

Syntax:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Example:

import React, { useState, useMemo } from 'react';

function ExpensiveCalculation({ num }) {
    const calculate = (n) => {
        console.log("Calculating...");
        return n * 2;
    };

    const result = useMemo(() => calculate(num), [num]);

    return <div>Result: {result}</div>;
}

How it Works:

  • useMemo memoizes the result of the calculation and only recalculates when one of the dependencies (e.g., num) changes.
  • This improves performance by preventing expensive calculations on every render.

6. useCallback Hook

Similar to useMemo, the useCallback hook memoizes functions. It’s particularly useful when you want to avoid unnecessary re-renders by ensuring that a function reference remains the same across renders.

Syntax:

const memoizedCallback = useCallback(() => {
    doSomething(a, b);
}, [a, b]);

Example:

import React, { useState, useCallback } from 'react';

function Button({ onClick, label }) {
    return <button onClick={onClick}>{label}</button>;
}

function App() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        setCount(count + 1);
    }, [count]);

    return (
        <div>
            <p>Count: {count}</p>
            <Button onClick={handleClick} label="Increment" />
        </div>
    );
}

How it Works:

  • useCallback ensures that handleClick is only recreated when count changes, preventing unnecessary re-renders of the Button component.
  • This is crucial for optimizing performance in components that rely on callbacks.

7. useRef Hook

The useRef hook returns a mutable ref object that persists across renders. It’s typically used to reference DOM elements or store mutable values that don’t trigger re-renders when changed.

Syntax:

const refContainer = useRef(initialValue);

Example:

import React, { useRef } from 'react';

function TextInputWithFocus() {
    const inputRef = useRef(null);

    const focusInput = () => {
        inputRef.current.focus();
    };

    return (
        <div>
            <input ref={inputRef} type="text" />
            <button onClick={focusInput}>Focus the input</button>
        </div>
    );
}

How it Works:

  • useRef allows you to access and manipulate DOM elements without causing re-renders.
  • In the example, the inputRef is used to focus the input element when the button is clicked.

Interview Questions Recap

Here are some commonly asked interview questions related to React Hooks:

  1. What is the difference between useState and useReducer, and when would you use one over the other?
  2. How does useEffect differ from lifecycle methods like componentDidMount and componentDidUpdate?
  3. What is the purpose of useMemo and useCallback, and how do they improve performance?
  4. How do useRef and useState differ in terms of state persistence?
  5. Explain how useContext simplifies the usage of context API compared to the traditional context methods.

Additional Essential JavaScript Interview Questions on Various Topics

React Js Interview questions:

Top Javascript Books to Read

Leave a Comment