DailyDevDiet

logo - dailydevdiet

Learn. Build. Innovate. Elevate your coding skills with dailydevdiet!

What are React Hooks?: A Comprehensive Guide

what are react hooks - dailydevdiet

React Hooks, introduced in React 16.8, revolutionized how developers build functional components by allowing them to use state and other features of React without writing a class. With hooks, developers can reuse logic across components, manage component lifecycle, and work more efficiently within functional components.

In this article, we’ll dive into the basics of React Hooks, explore the most common hooks, and understand how they enhance the React ecosystem.

What Are React Hooks?

Before React Hooks, managing state and lifecycle methods within React required the use of class components. Hooks allow functional components to handle side effects, state, and other React features, simplifying code and reducing boilerplate. Hooks follow these rules:

  • Only call hooks at the top level: Hooks should be used at the top of a functional component or in other hooks, not inside loops, conditions, or nested functions.
  • Only call hooks from React components: Hooks must be called inside a React functional component or custom hook.

Commonly Used React Hooks

Get ready to have a close look at the most commonly used hooks and their use cases.

1. useState

The useState is the most widely used React hook  which helps you to add state to functional components. It accepts an initial state and returns a pair: the current state value and a function to update that state.

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>
    );
}

In this example, useState initializes count to 0 and the setCount function updates the state when the button is clicked.

2. useEffect

The useEffect hook is primarily used to perform side effects in function components. Side effects could be data fetching, manual DOM manipulation, subscriptions, or logging.

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

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

    useEffect(() => {
        // Update the document title using the browser API
        document.title = `You clicked ${count} Times`;
    }, [count]); // Only re-run the effect if count changes

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

In this example, the useEffect hook updates the document title every time the count changes. The second argument, [count], ensures that the effect only runs when count is updated, optimizing performance by avoiding unnecessary re-renders.

3. useContext

useContext allows you to subscribe to React context without writing a class component. It enables components to access shared data like themes, user settings, or authentication status without prop drilling.

Example:
import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');

function ThemedButton() {
    const theme = useContext(ThemeContext);
    return <button style={{ background: theme === 'dark' ? '#333' : '#FFF' }}>I am styled by theme context!</button>;
}

Here, useContext accesses the value from ThemeContext, making the ThemedButton responsive to changes in theme without passing props through intermediary components.

4. useReducer

useReducer is a React hook which allows you to add a reducer to your component. It is similar to useState. But is more suitable for managing complex state logic involving multiple sub-values or when the next state depends on the previous one. It also works well when managing local component state and state transitions in a predictable way.

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>
    );
}

In this example, useReducer helps manage state transitions by dispatching actions (increment and decrement). It is commonly used for more complex state updates than what useState can handle. In continuation to what are React hooks, we discuss useRef next.

5. useRef

useRef creates a mutable object whose .current property persists through renders. It is often used for accessing DOM elements directly or storing mutable values that don’t trigger a re-render when updated.

Example:
import React, { useRef } from 'react';

function TextInputWithFocusButton() {
    const inputEl = useRef(null);
    const onButtonClick = () => {
        // Access the input element and focus it
        inputEl.current.focus();
    };
    return (
        <div>
            <input ref={inputEl} type="text" />
            <button onClick={onButtonClick}>Focus the input</button>
        </div>
    );
}

In this example, useRef is used to access the DOM input element directly and focus it when the button is clicked.

6. useMemo

useMemo memoizes expensive computations to optimize performance, ensuring that the calculation only re-runs when its dependencies change.

If you are interested in the practical application of the React hooks in real world, please visit here. We offer affordable web design and development using React and similar technolgoies.

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

function ExpensiveCalculationComponent({ a, b }) {
    const memoizedValue = useMemo(() => {
        return a + b; // Imagine a complex calculation here
    }, [a, b]);

    return <div>The result is: {memoizedValue}</div>;
}

In this example, useMemo ensures that the calculation only happens when a or b changes, preventing unnecessary re-calculations on every render.

7. useCallback

The useCallback React hook memoizes functions,  and then returns a memoized version that only changes if one of the dependencies has changed. It is used when you have a component in which the child is rerendering again and again without a need. This can optimize performance, especially when passing functions to child components.

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

const ExpensiveComponent = React.memo(({ onClick }) => {
    console.log("Rendered");
    return <button onClick={onClick}>Click Me</button>;
});

function App() {
    const [count, setCount] = useState(0);
    
    const increment = useCallback(() => {
        setCount((c) => c + 1);
    }, []); // Memoized function that only changes when dependencies change

    return (
        <div>
            <p>{count}</p>
            <ExpensiveComponent onClick={increment} />
        </div>
    );
}

8. useLayoutEffect

useLayoutEffect is a version of useEffect. It fires synchronously after all DOM mutations. It’s useful when you need to perform side effects that involve DOM measurements or manipulations that should happen before the browser paints.

import React, { useLayoutEffect, useRef } from 'react';

function Box() {
    const boxRef = useRef();

    useLayoutEffect(() => {
        console.log(boxRef.current.getBoundingClientRect());
    }, []);

    return <div ref={boxRef} style={{ width: '200px', height: '200px', backgroundColor: 'blue' }} />;
}

9. useImperativeHandle

useImperativeHandle is a React hook that customizes the instance value that is exposed when using ref. It’s useful when you want to expose certain actions of a component without giving full access to the DOM.

import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
    const inputRef = useRef();

    useImperativeHandle(ref, () => ({
        focus: () => inputRef.current.focus()
    }));

    return <input ref={inputRef} {...props} />;
});

function App() {
    const inputRef = useRef();

    return (
        <div>
            <CustomInput ref={inputRef} />
            <button onClick={() => inputRef.current.focus()}>Focus Input</button>
        </div>
    );
}

10. useDebugValue

useDebugValue is useful for custom hooks. It allows you to display a label in React DevTools for easier debugging.

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

function useCustomHook(value) {
    const [state, setState] = useState(value);
    useDebugValue(state ? 'On' : 'Off'); // Debug label
    return [state, setState];
}

function App() {
    const [isOn, toggle] = useCustomHook(false);

    return (
        <div>
            <p>{isOn ? 'On' : 'Off'}</p>
            <button onClick={() => toggle(!isOn)}>Toggle</button>
        </div>
    );
}

11. useTransition (React 18)

useTransition allows you to mark some updates as “non-urgent,” letting React update the UI without blocking high-priority updates (such as typing input).

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

function App() {
    const [input, setInput] = useState('');
    const [list, setList] = useState([]);
    const [isPending, startTransition] = useTransition();

    const handleChange = (e) => {
        setInput(e.target.value);
        startTransition(() => {
            setList([...Array(10000).keys()].map((item) => e.target.value));
        });
    };

    return (
        <div>
            <input type="text" value={input} onChange={handleChange} />
            {isPending ? <p>Loading...</p> : <ul>{list.map((item, i) => <li key={i}>{item}</li>)}</ul>}
        </div>
    );
}

12. useDeferredValue (React 18)

useDeferredValue allows you to defer a value, making React wait to update lower-priority renders until high-priority updates finish. This is useful for complex UI that may cause jank.

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

function App() {
    const [input, setInput] = useState('');
    const deferredInput = useDeferredValue(input);

    return (
        <div>
            <input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
            <p>Deferred Value: {deferredInput}</p>
        </div>
    );
}

13. useId (React 18)

useId generates unique IDs that are stable across server and client renders, useful for accessibility and form inputs.

import React, { useId } from 'react';

function App() {
    const id = useId();

    return (
        <div>
            <label htmlFor={id}>Name: </label>
            <input id={id} type="text" />
        </div>
    );
}

Best Practices for Using Hooks

  1. Keep Components Simple: Avoid overly complex logic inside hooks. Break down functionality into multiple hooks if necessary.
  2. Avoid Side Effects in Render: Only use hooks like useEffect or useLayoutEffect for side effects, not in the render function itself.
  3. Custom Hooks: Create reusable logic using custom hooks to keep your components clean and DRY (Don’t Repeat Yourself).

Conclusion

This concludes our discussion on what are React hooks. React Hooks have revolutionized how developers build and manage state in functional components. From basic state management with useState to the more advanced performance optimizations of useTransition and useDeferredValue, hooks have simplified component logic and enhanced scalability. Understanding both the foundational and newer hooks is essential to writing efficient, clean, and maintainable React code.

P.S.: The React is getting popular day by day. If you are curious about the future of React, read here:

Scroll to Top