Hooks in React

Harshit Bansal's photo
·

20 min read

Hooks in React

What are React Hooks?

React Hooks are special functions introduced in React 16.8 that allow you to use React features like state and lifecycle methods in functional components. Before hooks, these features were only available in class components. Hooks are both in-built and custom (user defined).

When to use React Hooks?

If you’ve created a functional component in React and later need to add state or other React features that were previously only available in class components, you no longer have to convert it into a class component. With React Hooks, you can add state and other features directly to a functional component.

Some Built-in React Hooks

  1. State Hooks

    State lets a component “remember” information like user input. For example, a form component can use state to store the input value, while an image gallery component can use state to store the selected image index.

    To add state to a component, use one of these Hooks:

    • useState declares a state variable that you can update directly.

    • useReducer declares a state variable with the update logic inside a reducer function.

  1. Context Hooks

    Context lets a component receive information from distant parents without passing it as props. For example, your app’s top-level component can pass the current UI theme to all components below, no matter how deep.

    • useContext reads and subscribes to a context.
  1. Ref Hooks

    Ref hooks allow you to create a reference to a DOM element or store mutable values that persist across renders. Unlike with state, updating a ref does not re-render your component.

    • useRef declares a ref. You can hold any value in it, but most often it’s used to hold a DOM node.

    • useImperativeHandlelets you customize the ref exposed by your component. This is rarely used.

  1. Effect Hooks

    Effect hooks let you perform side effects in functional components. Side effects include tasks like fetching data from an API, interacting with the DOM (e.g., updating the page title), setting up subscriptions or timers.

    • useEffect connects a component to an external system.

    • useLayoutEffect fires before the browser repaints the screen. You can measure layout here.

    • useInsertionEffect fires before React makes changes to the DOM. Libraries can insert dynamic CSS here.

Note : Before hooks, side effects were handled using lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount in class components.

  1. Performance Hooks

    A common way to optimize re-rendering performance is to skip unnecessary work. For example, you can tell React to reuse a cached calculation or to skip a re-render if the data has not changed since the previous render. To skip calculations and unnecessary re-rendering, use one of these Hooks:

    • useMemo lets you cache the result of an expensive calculation.

    • useCallback lets you cache a function definition before passing it down to an optimized component.

Sometimes, your app has to do heavy tasks, like showing search results for a big dataset or rendering complex charts. These tasks can slow down things like typing in a search bar, making your app feel laggy.

  • useTransition allows React to pause the heavy work and focus on the important stuff (like updating the search bar as you type).

  • useDeferredValue Lets you delay updating the slow parts (like search results) so that quick actions (like typing) don’t get blocked.

Some Other Hooks

These Hooks are mostly useful to library authors and aren’t commonly used in the application code.

  • useDebugValue lets you customize the label React DevTools displays for your custom Hook.

  • useId lets a component associate a unique ID with itself. Typically used with accessibility APIs.

  • useSyncExternalStore lets a component subscribe to an external store.


React Hooks in Detail

Let’s now study about all the hooks provided by React.

Note : All the hooks can only be called at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.

  1. useState

The useState hook in React is used to add state to functional components. It allows you to create variables that React watches for changes. When the state changes, the component automatically re-renders to reflect those changes.

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

How does it works ?

1. Initialize State: You provide an initial value for the state. Can be a static value or a function for lazy initialization.

  • Static: useState(0)

  • Lazy: useState(() => calculateInitialValue()) (runs only once).

2. Get State and Updater Function: useState returns two things:

  • The current state value : During the first render, it will match the initialState you have passed.

  • A set function to update the state : It lets you update the state to a different value and trigger a re-render. It accepts a parameter which indicates the new value of the state.

When to use it ?

  • For simple, local state management (e.g., counters, form inputs).

  • When the component’s state doesn’t need to be shared globally.

Caveats

  1. The setState function only updates the state variable for the next render. If you read the state variable after calling the setState function, you will still get the old value that was on the screen before your call.

  2. If the new value you provide is identical to the current state, React will skip re-rendering the component and its children**.** This is an optimization. Although in some cases React may still need to call your component before skipping the children, it shouldn’t affect your code.

  3. React batches state updates. It updates the screen after all the event handlers have run and have called their setState functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use flushSync.


  1. useEffect

    The useEffect hook handles the effects of the dependency array. The useEffect Hook allows us to perform side effects on the components. fetching data, directly updating the DOM and timers are some side effects. It is called every time any state if the dependency array is modified or updated.

     useEffect(()=>{
         //callback function 
     }, [dependency array]);
    

    Note : useEffect returns undefined.

How does it works ?

The useEffect hook takes two arguments:

  • A callback function: Contains the side effect code. Your setup function may also optionally return a cleanup function. When your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. After your component is removed from the DOM, React will run your cleanup function.

  • A dependency array (optional): Tells React when to re-run the effect. The callback function will be again executed if there are any changes to the states provided in the dependency array.

When does it work ?

  • Without a dependency array: Runs after every render (both initial render and updates).

  • With an empty dependency array []: Runs only after the initial render.

  • With specific dependencies [dep1, dep2]: Runs only when the listed dependencies change.

Caveats

  1. Effects only run on the client. They don’t run during server rendering.

  2. If your effect involves resources that need to be cleaned up (e.g., timers, subscriptions, event listeners), you can return a cleanup function from useEffect.


  1. useContext

    useContext is a React hook that allows you to consume context within a functional component. It provides a way to share values (like state or functions) across your component tree without having to pass props down manually at every level.

How does it works ?

useContext is used with Context objects. Context helps you avoid prop drilling, where props are passed down through many layers of components just to reach a deeply nested component.

Creating a Context

Before using useContext, you need to create a context using createContext() and provide it in a parent component using the Context.Provider. It can take an argument as initialValue.

const ThemeContext = createContext(); //Create context

const App = () => {
    const [theme, setTheme] = useState('light');
    return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

ThemeContext.Provider wraps the components that need access to the context and provides a value to be shared.

Consuming a Context

To access the context value in any child component, use the useContext hook.

function ChildComponent() {
  const { theme, setTheme } = useContext(ThemeContext); // Access Context value

  return (
    <div>
      <p>Current theme: {theme}</p> <!-- Using theme from Context -->
      <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
        Toggle Theme
      </button>
    </div>
  );
}

When to use it ?

  • Global State Management: To avoid prop drilling, useContext helps manage values like authentication status, theme, user settings, or language preferences at the top level and easily access them across components.

  • Shared Data: For scenarios where multiple components need access to the same data, like a cart in an e-commerce app, you can provide that data at a high level and consume it in various child components.

Note : useContext returns the context value for the calling component. It is determined as the value passed to the closest SomeContext.Provider above the calling component in the tree. If there is no such provider, then the returned value will be the defaultValue you have passed to createContext for that context. The returned value is always up-to-date. React automatically re-renders components that read some context if it changes.

Caveats

  • useContext() call in a component is not affected by providers returned from the same component. The corresponding <Context.Provider> needs to be above the component doing the useContext() call.

  • React automatically re-renders all the children that use a particular context starting from the provider that receives a different value.

  • useContext is only used in functional components. Class components would use the Context.Consumer component instead.


  1. useActionState

useActionState is a Hook that allows you to update state based on the result of a form action. It is used to simplify the management of form submissions and asynchronous actions. It works by allowing you to track the state of an action—whether it’s pending, successful, or encountered an error—while also managing the form data.

const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);

It receives three parameters :

  • fn: The function to be called when the form is submitted or button pressed. When the function is called, it will receive the previous state of the form (initially the initialState that you pass, subsequently its previous return value) as its initial argument, followed by the arguments that a form action normally receives.

  • initialState: The value you want the state to be initially. It can be any serializable value. This argument is ignored after the action is first invoked.

  • permalink (optional) : A string containing the unique page URL that this form modifies. For use on pages with dynamic content (eg: feeds) in conjunction with progressive enhancement: if fn is a server function and the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL, rather than the current page’s URL. Ensure that the same form component is rendered on the destination page (including the same action fn and permalink) so that React knows how to pass the state through. Once the form has been hydrated, this parameter has no effect.

Note: useActionState returns an array with the following values:

  1. The current state : During the first render, it will match the initialState you have passed. After the action is invoked, it will match the value returned by the action.

  2. A new action that you can pass as the action prop to your form component or formAction prop to any button component within the form.

  3. The isPending flag that tells you whether there is a pending Transition.


  1. useCallback

    useCallback is a React Hook that lets you cache a function definition between re-renders. It helps optimize performance by preventing unnecessary re-creations of functions on every render, which can be particularly useful in cases where functions are passed as props to child components, triggering unnecessary re-renders. It ensures that a function is not redefined unless certain dependencies change.

const memoizedCallback = useCallback(
  () => { /* function body */ },
  [dependency1, dependency2] // List of dependencies that will trigger re-creation
);

  1. useDeferredValue

    useDeferredValue improves the performance of updates in situations where part of the UI can be deferred. It allows React to delay the update of certain state values, prioritizing more critical updates (like user input) and deferring less important updates (like rendering large data sets or non-critical UI changes). This helps create a smoother experience, especially during heavy computation or rendering tasks.

const deferredValue = useDeferredValue(value);

The useDeferredValue hook takes a state value and returns a deferred version of that value. When the value changes, React will only update the deferred value in the background, ensuring that the user interface remains responsive by allowing more critical updates to render first.

Parameters

  • value: The value you want to defer. It can have any type.

  • initialValue (optional) : A value to use during the initial render of a component. If this option is omitted, useDeferredValue will not defer during the initial render, because there’s no previous version of value that it can render instead.

Note: During the initial render, the returned deferred value will be the initialValue, or the same as the value you provided. During updates, React will first attempt a re-render with the old value (so it will return the old value), and then try another re-render in the background with the new value (so it will return the updated value).

Caveats

  1. useDeferredValue does not by itself prevent extra network requests.

  2. When an update is inside a Transition, useDeferredValue always returns the new value and does not spawn a deferred render, since the update is already deferred.

  3. The values you pass to useDeferredValue should either be primitive values (like strings and numbers) or objects created outside of rendering. If you create a new object during rendering and immediately pass it to useDeferredValue, it will be different on every render, causing unnecessary background re-renders.

  4. The background re-render caused by useDeferredValue does not fire Effects until it’s committed to the screen. If the background re-render suspends, its Effects will run after the data loads and the UI updates.


  1. useId

The useId hook helps in generating unique IDs for components. It is particularly useful when you need unique identifiers for elements like form controls or ARIA attributes that need to be associated with labels and other elements.

const id = useId();

Note : useId returns a unique ID string associated with this particular useId call in this particular component.

It generates stable and unique IDs that persist across renders and ensure that components get unique identifiers without the need for manually managing them. useId should not be used to generate keys in a list.


  1. useMemo

    useMemo is a React hook that helps optimize performance by memoizing (remembering) the result of a computation so that it doesn’t have to be recalculated on every render. This can be particularly useful for expensive computations or large data sets.

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

It ensures that a value is only recomputed when its dependencies change, preventing unnecessary recalculations.


  1. useOptimistic

The useOptimistic hook in React is a way to implement an “optimistic UI,” where the UI is updated immediately after an action is initiated, before the server’s response is received. This pattern provides users with immediate feedback, making the app feel faster and more responsive.

  const [optimisticState, addOptimistic] = useOptimistic(currentState, updateFn);

How does it works ?

The hook works by allowing the immediate update of the state with a “temporary” optimistic version of the data. Then, once the server responds, you can update the state again with the real response. If an error occurs, the UI can be reverted to its previous state to reflect the failure.

Parameters

  • currentState: The current state of the application.

  • updateFn(currentState, optimisticValue): a function that takes the current state and the optimistic value passed to addOptimistic and returns the resulting optimistic state. It must be a pure function. updateFn takes in two parameters. The currentState and the optimisticValue. The return value will be the merged value of the currentState and optimisticValue.

Note : It returns the optimistic state and a function to change the optimistic state.

Here is an example of a shopping cart

import { useState } from 'react';
import { useOptimistic } from 'react';

const [cartItems, setCartItems] = useState([]);
const [optimisticState, addOptimistic] = useOptimistic(cartItems, (currentItems, newItem) => [...currentItems, newItem]);

const addItemToCart = async (item) => {
  // Optimistic update
  addOptimistic(item); // Add item to UI immediately

  try {
    const updatedCart = await addItemToServer(item); // Simulate server call
    setCartItems(updatedCart); // If successful, update with server response
  } catch (error) {
    console.error("Failed to add item", error);
    // Rollback by restoring the previous state
    setCartItems(cartItems); // Revert the optimistic state to previous state
  }
};

  1. useReducer

    useReducer is used to manage more complex state logic compared to useState. It provides an alternative to useState when the state transitions involve multiple actions or are difficult to handle with simple state updates. useReducer uses a reducer function, which takes the current state and an action, then returns a new state based on the action’s type and payload. This is similar to how reducers work in Redux but is built directly into React.

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer: A function that determines how the state changes based on an action.

  • initialState: The initial state value.

  • state: The current state.

  • dispatch: A function to send actions to the reducer.

Example of a Reducer Function

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

Caveats

  1. The dispatch function only updates the state variable for the next render. If you read the state variable after calling the dispatch function, you will still get the old value that was on the screen before your call.

  2. If the new value you provide is identical to the current state, React will skip re-rendering the component and its children. This is an optimization. React may still need to call your component before ignoring the result, but it shouldn’t affect your code.


  1. useRef

    useRef is a React Hook that provides a way to interact with and persist values across renders without triggering a re-render. It is often used to directly interact with DOM elements, store mutable values, or maintain a reference to a value that doesn’t change frequently.

const ref = useRef(initialValue);

Note : useRef returns an object with a single property → current : Initially, it’s set to the initialValue you have passed. You can later set it to something else. If you pass the ref object to React as a ref attribute to a JSX node, React will set its current property.

Caveats

  1. You can mutate the ref.current property. Unlike state, it is mutable. However, if it holds an object that is used for rendering (for example, a piece of your state), then you shouldn’t mutate that object.

  2. When you change the ref.current property, React does not re-render your component. React is not aware of when you change it because a ref is a plain JavaScript object.

  3. Do not write or read ref.current during rendering, except for initialization. This makes your component’s behavior unpredictable.


  1. useTransition

    The useTransition hook in React is used to manage non-blocking state updates for smoother user interfaces. It helps prioritize updates, allowing essential operations (like typing in an input) to remain responsive while deferring less critical updates (like rendering a chart or a list). This improves the user experience in scenarios where some updates are computationally expensive or take time to complete.

const [isPending, startTransition] = useTransition();

Note : It returns an array with exactly two items :

  • The isPending flag that tells you whether there is a pending Transition.

  • startTransition: A function to wrap non-urgent updates in.

Caveats

  1. useTransition is a Hook, so it can only be called inside components or custom Hooks. If you need to start a Transition somewhere else (for example, from a data library), call the standalone startTransition instead.

  2. You can wrap an update into a Transition only if you have access to the set function of that state. If you want to start a Transition in response to some prop or a custom Hook value, try useDeferredValue instead.

  3. The function you pass to startTransition is called immediately, marking all state updates that happen while it executes as Transitions. If you try to perform state updates in a setTimeout, for example, they won’t be marked as Transitions.

  4. The startTransition function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. If the linter lets you omit a dependency without errors, it is safe to do.

  5. Transition updates can’t be used to control text inputs.


  1. useFormStatus

    Note : It is a React DOM Hook. The react-dom package contains Hooks that are only supported for web applications (which run in the browser DOM environment). These Hooks are not supported in non-browser environments like iOS, Android, or Windows applications

useFormStatus is a Hook that gives you status information of the last form submission.

const { pending, data, method, action } = useFormStatus();

How does it works ?

This hook must be used inside a component rendered within a <form>. It provides an object with properties like:

pending: A boolean. If true, this means the parent <form> is pending submission. Otherwise, false.

data: An object implementing the FormData interface that contains the data the parent <form> is submitting. If there is no active submission or no parent <form>, it will be null.

method: A string value of either 'get' or 'post'. This represents whether the parent <form> is submitting with either a GET or POST HTTP method. By default, a <form> will use the GET method and can be specified by the method property.

action: A reference to the function passed to the action prop on the parent <form>. If there is no parent <form>, the property is null. If there is a URI value provided to the action prop, or no action prop specified, status.action will be null.

Caveats

  1. The useFormStatus Hook must be called from a component that is rendered inside a <form>.

  2. useFormStatus will only return status information for a parent <form>. It will not return status information for any <form> rendered in that same component or children components.


Custom Hooks

A custom hook in React is a JavaScript function that allows you to reuse logic across multiple components. React hooks like useState, useEffect, and useContext are commonly used within custom hooks to encapsulate reusable logic that doesn’t directly involve rendering UI but can be used by multiple components.

Custom hooks offer a way to share functionality without repeating code, keeping components clean and modular.

Why to use them ?

  • Reusability : Custom hooks allow you to extract logic from components and reuse it in different places, promoting DRY (Don’t Repeat Yourself) code.

  • Separation of Concerns: By isolating non-UI logic in custom hooks, components focus only on rendering the UI.

  • Encapsulation: Custom hooks enable the encapsulation of related logic, making it easier to manage and test.

  • Simplifies Complex Logic: Custom hooks allow complex logic, such as handling form inputs or managing APIs, to be shared across components without redundancy.

  • Easier Testing: Since custom hooks are functions, they can be independently tested, making it easier to ensure that your logic works as expected.

  • Better Code Organization: You can group related logic into a custom hook, making your components more readable and maintainable.

Creating a Custom Hook

Let’s create a custom hook for a restaurant website which fetches the menu of any particular restaurant from a API and using the restaurant ID.

const useRestaurantMenu = (resId) => {
    const [resInfo, setResInfo] = useState(null);

    useEffect(()=>{
        fetchData();
    }, [])

    const fetchData = async () => {
        const data = await fetch(API + resId);
        const json = await data.json();
        setResInfo(json);
    };

    return resInfo;
}

export default useRestaurantMenu;

Now we can use our custom hook useRestaurantMenu in other components to fetch the menu of restaurants by importing it.

Let’s create one more custom hook to check if the user is online or offline.

const useOnlineStatus = () => {
    const [onlineStatus, setOnlineStatus] = useState(false);

    useEffect(() => {
        window.addEventListener("offline", () => {
            setOnlineStatus(false);
        });

        window.addEventListener("online", () => {
            setOnlineStatus(true);
        });
    }, []);

    return onlineStatus;
}

export default useOnlineStatus;

Caveats

  1. Custom hooks must follow React’s rules of hooks, meaning they can only be called at the top level of a component or another hook.

  2. Custom hooks can return any value, object, array, or function that the component needs.

  3. Make sure that logic inside custom hooks doesn’t lead to side effects unless it’s part of React’s lifecycle, such as in useEffect.

  4. The name of the Custom Hook should start with the prefix use (Recommended but not mandatory).