Back Home.

11 essential React hooks for frontend development

Cover Image for 11 essential React hooks for frontend development
Admin
Admin

11 Essential React Hooks for Frontend Development

React, a populur JavaScript library for building user interfaces, has revolitionized the way developers create dynamic and interactive web applications. One of the key features that make React so powerfull is its hooks system, which allows developers to tap into React's functionality and build more robust and maintainable applications.

In this articel, we'll explore the top 11 essential React hooks that every frontend developer should know. These hooks will help you manage state, handle side effects, and optimize your application's performance.

useState: The Building Block of State Management

The first and most fundamental hook is useState. This hook allows you to add React state to function components. With useState, you can create a state variable and an updating function to modify the state.

For example, let's create a simple counter component that increments when you click a button:

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

In this example, useState creates a state variable count and an updating function setCount. We use the setCount function to increment the count when the button is clicked.

useEffect: Handling Side Effects

The next essential hook is useEffect. This hook allows you to run some code after rendering, making it perfect for handling side effects such as making API calls, setting timers, or updating the DOM.

For example, let's create a component that fetches data from an API when it mounts:

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []);

  return <div>Data: {data}</div>;
}

In this example, useEffect runs the code inside the callback function when the component mounts. The second argument, an empty array, tells React to run the effect only once, when the component mounts.

useContext: Sharing State Across Components

useContext is a hook that allows you to share state across components without passing props down manually. This hook is particularly useful when you need to share state between components that are not directly related.

For example, let's create a theme context that allows components to access the current theme:

import { createContext, useContext } from 'react';

const ThemeContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
      <Sidebar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);

  return <div>Toolbar (Theme: {theme})</div>;
}

function Sidebar() {
  const theme = useContext(ThemeContext);

  return <div>Sidebar (Theme: {theme})</div>;
}

In this example, useContext allows the Toolbar and Sidebar components to access the current theme without passing props down manually.

useReducer: Managing Complex State

When you need to manage complex state with multiple sub-states, useReducer is the hook to use. This hook is similar to useState, but it uses a reducer function to manage the state.

For example, let's create a todo list component that uses useReducer to manage the todos:

import { useReducer } from 'react';

const initialState = {
  todos: [],
  filter: 'all'
};

function reducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.todo] };
    case 'REMOVE_TODO':
      return { ...state, todos: state.todos.filter(todo => todo.id !== action.id) };
    case 'FILTER TODOS':
      return { ...state, filter: action.filter };
    default:
      return state;
  }
}

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

  return (
    <div>
      <h1>Todo List ({state.filter})</h1>
      <ul>
        {state.todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
      <button onClick={() => dispatch({ type: 'ADD_TODO', todo: { id: 1, text: 'New Todo' } })}>
        Add Todo
      </button>
      <button onClick={() => dispatch({ type: 'FILTER TODOS', filter: 'completed' })}>
        Filter completed
      </button>
    </div>
  );
}

In this example, useReducer uses the reducer function to manage the state of the todo list. The dispatch function is used to send actions to the reducer, which updates the state accordingly.

useMemo and useCallback: Optimizing Performance

useMemo and useCallback are two hooks that help optimize your application's performance by memoizing values and functions.

useMemo memoizes a value, so it's not recomputed on every render. For example, let's create a component that memoizes a computationally expensive function:

import { useMemo } from 'react';

function computeExpensiveValue() {
  // Simulate an expensive computation
  return Math.random() * 1000;
}

function MyComponent() {
  const expensiveValue = useMemo(computeExpensiveValue, []);

  return <div>Expensive Value: {expensiveValue}</div>;
}

In this example, useMemo memoizes the computeExpensiveValue function, so it's only computed once, on the first render.

useCallback memoizes a function, so it's not recreated on every render. For example, let's create a component that memoizes a callback function:

import { useCallback } from 'react';

function MyComponent() {
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []);

  return <button onClick={handleClick}>Click me!</button>;
}

In this example, useCallback memoizes the handleClick function, so it's only created once, on the first render.

useRef: Creating References

useRef creates a reference to a DOM node or a value that persists across renders. This hook is useful when you need to imperatively update the DOM or access a value that's not dependent on the component's state.

For example, let's create a component that uses useRef to create a reference to an input element:

import { useRef } from 'react';

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

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

In this example, useRef creates a reference to the input element, which is stored in the inputRef variable. We can then use the inputRef.current property to access the input element and imperatively update it.

useImperativeHandle: Customizing Refs

useImperativeHandle customizes the instance value that's exposed to parent components when using ref. This hook is useful when you need to expose a customized API to parent components.

For example, let's create a component that uses useImperativeHandle to expose a customized API:

import { forwardRef, useImperativeHandle } from 'react';

function FancyInput(props, ref) {
  const inputRef = useRef(null);

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

  return <input ref={inputRef} type="text" />;
}

const FancyInputComponent = forwardRef(FancyInput);

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

  return (
    <div>
      <FancyInputComponent ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus input</button>
      <button onClick={() => inputRef.current.blur()}>Blur input</button>
    </div>
  );
}

In this example, useImperativeHandle customizes the instance value exposed to the parent component, allowing it to access the customized API.

useLayoutEffect: Managing Layout Effects

useLayoutEffect is similar to useEffect, but it fires after all DOM mutations. This hook is useful when you need to make layout changes or measure the DOM.

For example, let's create a component that uses useLayoutEffect to measure the size of an element:

import { useLayoutEffect, useState } from 'react';

function MeasureComponent() {
  const [size, setSize] = useState({ width: 0, height: 0 });

  useLayoutEffect(() => {
    const element = document.getElementById('my-element');
    setSize({ width: element.offsetWidth, height: element.offsetHeight });
  }, []);

  return (
    <div id="my-element">
      <p>Width: {size.width}</p>
      <p>Height: {size.height}</p>
    </div>
  );
}

In this example, useLayoutEffect measures the size of the element after it's rendered and updates the state accordingly.

useDebugValue: Debugging Hooks

useDebugValue displays a label for a value in React DevTools. This hook is useful when you need to debug complex state or side effects.

For example, let's create a component that uses useDebugValue to display a label for a state value:

import { useState, useDebugValue } from 'react';

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

  useDebugValue(count, count => `Count: ${count}`);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

In this example, useDebugValue displays a label for the count state value in React DevTools, making it easier to debug the component's state.

useId: Generating Unique IDs

useId generates a unique ID that can be used to identify elements in the DOM. This hook is useful when you need to generate IDs for dynamically generated content.

For example, let's create a component that uses useId to generate a unique ID for a list item:

import { useId } from 'react';

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

  return <li id={id}>List item {id}</li>;
}

In this example, useId generates a unique ID for the list item, which can be used to identify it in the DOM.

Conclusion

In this articel, we've explored the top 11 essential React hooks that every frontend developer should know. From managing state with useState and useReducer, to handling side effects with useEffect, and optimizing performance with useMemo and useCallback. We've also covered hooks like useContext, useRef, useImperativeHandle, useLayoutEffect, useDebugValue, and useId, which help you manage complex application logic. By mastering these essential hooks, you'll be able to build more robust, maintainable, and efficient React applications.