Easily restore your project to a previous version with our new Instant One-click Backup Recovery

React Hooks: The only guide you’ll ever need

Understand React Hooks, such as useState, useEffect, useReducer, and more, with practical examples and tips for efficient React development.
Chidi Eze

Chidi Eze

Aug 19, 2024
Mobile image

React Hooks are powerful features introduced in React 16.8 that let you "Hook into" React state and lifecycle features from function components.

By eliminating the need for class-based components, Hooks have brought in a new era of declarative and composable React development. They empower you to build more concise, reusable, and testable components, enhancing overall code quality and developer experience.

In this blog post, you’ll discover different types of Hooks and their benefits and drawbacks, and the code examples provided will help you understand how to use them.

#Rules of React Hooks

React Hooks come with two rules that must be followed to ensure their proper functioning and avoid errors:

  1. Call Hooks at the top level: Call Hooks outside loops, conditions, or nested functions.
  2. Call Hooks from Reactfunctions: Only use Hooks within React function components or custom Hooks.

These rules ensure that the order of Hook calls remain consistent across renders, preventing React from getting confused about which state corresponds to which Hook.

Internal image_ React hooks.png

Let's clone this Hygraph project to set up the environment for discussing the practical applications of some common Hooks. This will serve as our playground for demonstrating these Hooks in action. Now, let's get started with the useState Hook.

#The useState Hook

This React Hook allows you to add state to functional components. It provides a way to store and manage values that can change over time within your component, triggering re-renders whenever the state is updated.

Example

function Counter() {
const [count, setCount] = useState(0); // Initial state is 0
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}

In the code above, the useState Hook creates an array with the state value (count) and a function to update the state (setCount). The state has an initial value of 0, but it could also be null, an empty array ([]), an object, or any value that suits your needs.

The useState Hook is best suited for managing state values and provides an intuitive way to manage states in functional components. If you call the setCount function with a new value, your state will be updated, and React will automatically re-render the component with the updated state, ensuring that your UI is always up-to-date. Let's discuss the useEffect Hook next.

#The useEffect Hook

The useEffect Hook is a powerful React Hook that allows functional components to perform side effects. Side effects are operations that interact with the outside world or have an impact beyond the component's scope, such as fetching data from an API, directly updating the DOM, setting up subscriptions, and so on. Here's a quick implementation example:

function App() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Dependency array
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}

The useEffect takes two arguments:

  • A function that contains the side effect logic (in this case, updating the document title)
  • An optional dependency array (in this case, [count])

The side effect function is called whenever the component mounts, whether the dependency array is present or not. It uses the current value of the count variable to update the document title.

function App() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
// Cleanup function
return () => {
document.title = 'Default Title';
};
}, [count]); // Dependency array
//Component return() here
}

The cleanup function (returned from the side effect function) is called when the component is about to be unmounted or when a new side effect is about to be executed. In this case, it resets the "React App" document title.

The dependencies array [] instructs React to execute the useEffect side effect function only when the values inside it change between renders, in our case [count]. If no dependency array is provided, the effect will run after every render, whether the values changed or not.

Internal image_ React hooks (1).png

While `useEffect` is a powerful React Hook, it can also introduce complexity, especially when dealing with multiple effects, complex dependency arrays, or intricate cleanup logic. This can make your code harder to reason about and maintain, which is why there are many nuanced opinions about it in the React community.

For a more practical implementation of the useEffect and useState Hooks, let's optimize the Hygraph Next.js project you cloned earlier. Update the product/[slug]/page.jsx with the following snippets:

'use client';
import { useState, useEffect } from 'react';
import { GraphQLClient } from 'graphql-request';
import Link from 'next/link';
const hygraph = new GraphQLClient(
'https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master'
);
const QUERY = `
query ProductPageQuery($slug: String!) {
product(where: { slug: $slug }) {
name
description
price
}
}
`;
export default function Product({ params }) {
const [product, setProduct] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchProduct = async () => {
try {
const { product } = await hygraph.request(QUERY, { slug: params.slug });
setProduct(product);
} catch (error) {
console.error('Error fetching product:', error);
} finally {
setIsLoading(false);
}
};
fetchProduct();
}, [params.slug]); // Fetch only when slug changes
return (
<>
<Link href="/">Home</Link>
{isLoading ? (
<p>Loading...</p>
) : (
<>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>{product.price / 100}</p>
</>
)}
</>
);
}

The code has been refactored to utilize React's useState Hook for managing the product data, and a loading state (isLoading ) was also added. This enables efficient re-renders only when necessary and provides a loading indicator to improve the user experience.

Additionally, the useEffect Hook fetches product data solely when the component mounts or when the product's slug changes, preventing redundant API calls. The code also includes error handling and query optimization for robustness and readability.

#The useReducer Hook

The useReducer is another React Hook used for state management in functional components. It's an alternative Hook like useState when dealing with complex state management. In cases where your state logic involves multiple interconnected values or when the next state is determined by both the previous state and a specific action, useReducer excels.

This allows for a more structured and centralized approach to state management, encapsulating complex logic within a reducer function.

How it works:

The reducer() takes the current state and an action object as arguments. The action object has a type property (and can also have a payload). Based on the action.type, the reducer determines how to update the state and returns the new state.

The useReducer Hook is then called with the reducer function and an initial state. Afterwards, it returns an array with two values:

  • state: This is the current state managed by the reducer.
  • dispatch: This function triggers state updates by sending action objects to the reducer.

To update the state, you only need to call the dispatch function with an action object. The reducer will then listen to the action and update the state accordingly, causing React to re-render the corresponding component.

Example

In the example, we will optimize fetching all products with the useReducer and useEffect Hooks. Still in our Hygraph project, update the app/page.jsx with the following code snippets:

"use client"
import { useReducer, useEffect } from 'react';
import { GraphQLClient, gql } from 'graphql-request';
import Link from 'next/link';
const hygraph = new GraphQLClient(
'https://api-eu-central-1.hygraph.com/v2/ck8sn5tnf01gc01z89dbc7s0o/master'
);
const QUERY = `
{
products {
slug
name
id
}
}
`;
const initialState = {
products: [],
loading: true,
error: null
};
function reducer(state, action) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { products: action.payload, loading: false, error: null };
case 'FETCH_ERROR':
return { products: [], loading: false, error: action.payload };
default:
return state;
}
}
export function generateMetadata() {
return { title: 'Products' };
}
export default function Page() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const fetchProducts = async () => {
try {
const { products } = await hygraph.request(QUERY);
dispatch({ type: 'FETCH_SUCCESS', payload: products });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error.message });
}
};
fetchProducts();
}, []);
return (
<div>
<h1>Products</h1>
{state.loading ? (
<p>Loading...</p>
) : state.error ? (
<p>Error: {state.error}</p>
) : (
<ul>
{state.products.map(({ slug, name, id }) => (
<li key={id}>
<Link href={`/products/${slug}`}>{name}</Link>
</li>
))}
</ul>
)}
</div>
);
}

By incorporating useReducer, the code becomes more organized, the state management is centralized, and error handling and loading states are explicitly managed.

Another example where useReducer truly shines is when implementing a shopping cart component. You can employ the useReducer Hook to manage adding, removing, and modifying items within the cart. This approach helps maintain a clean and efficient state structure; however, useState might be a better alternative for simple state updates. Next, let's discuss the useCallback Hook.

#The useCallback Hook

The useCallback Hook memoizes a callback function to ensure that it is not recreated on every render unless its dependencies change. This capability can be crucial for improving the performance of React applications, especially when dealing with complex components and frequent updates.

Here's how it works:

  • It takes two arguments, a callback function and an array of dependencies, similar to the useEffect Hook.
  • On the initial render, useCallback returns the callback function you passed.
  • On subsequent renders, useCallback compares the dependencies with the previous render's dependencies.
  • If none of the dependencies have changed, the useCallback Hook will return the same function.
  • If any dependency has changed, useCallback returns a new memoized version of the callback function.

Here's an example that demonstrates how the useCallback Hook can be leveraged to optimize further the imaginary counter component we had in our initial examples:

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

In this example, we define two callback functions, incrementCounter and decrementCounter, using the useCallback Hook. We pass [count] as the dependency array so that the callbacks will only be recreated if the count value changes.

This way (memoizing the callbacks with the useCallback Hook), we ensure that the callbacks are not unnecessarily recreated on every render, which could lead to unnecessary re-renders of child components that rely on these callbacks. Here are some primary use cases for the useCallback Hook:

  • They optimize performance by preventing unnecessary re-renders of child components that depend on callback functions.
  • They ensure reference equality for callback functions passed as dependencies to other Hooks or memoized components.
  • They help to avoid recreating expensive functions on every render, especially when passed as props to child components.

Like useCallback, the useMemo Hook also helps optimize React applications by caching the results of complex calculations. Let's discuss it in detail.

#The useMemo Hook

The useMemo Hook is another performance optimization Hook in React. It's designed to memoize the result of a calculation or an expensive React function to prevent it from being recomputed on every render of your component. Like the useCallback Hook, it takes two arguments - a function that performs the computation and an array of dependencies. Here's how it works:

  • It only recomputes the memoized value when one of its dependencies changes.
  • It returns the memoized value, which remains the same between re-renders unless the dependencies change.
  • It is primarily used for performance optimization, not as a semantic guarantee. React can "forget" some previously memoized values in situations like freeing up memory for offscreen components, etc.

Here's an improved version of our counter component that uses the useMemo Hook to enhance its performance.

import { useState, useMemo } from 'react';
function ExpensiveCalculation({ number }) {
// Simulating an expensive calculation
const calculateFactorial = (num) => {
if (num === 0 || num === 1) return 1;
let result = 1;
for (let i = 2; i <= num; i++) {
result *= i;
}
return result;
};
const factorial = useMemo(() => calculateFactorial(number), [number]);
return <div>Factorial of {number} is {factorial}</div>;
}
function Counter() {
const [count, setCount] = useState(0);
const [number, setNumber] = useState(5);
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment Counter</button>
<h2>Factorial Calculator</h2>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
/>
<ExpensiveCalculation number={number} />
</div>
);
}
export default Counter;

In this example, the result of the ExpensiveCalculation component was memoized. The real benefit of using useMemo in this scenario is that the factorial calculation is only performed when the number changes. As a result, incrementing the counter doesn't trigger a recalculation of the factorial.

When used correctly, the useMemo Hook can significantly improve the speed and responsiveness of your applications. However, it can also negatively impact your app. Here are some notable considerations.

  • In strict mode, React calls the calculation function twice to help identify accidental impurities.
  • Overuse or unnecessary memoization can hurt performance due to difficulty maintaining the cache. Moving on, let's explore the useContext Hook as an alternative approach to managing state in React applications.

#The useContext Hook

Data in React components flow in one direction, from parent to child, through props. The useContext Hook in React lets you access data stored in a central location, called a context, without needing to manually pass props down at every level. This is useful for managing information that needs to be available across different parts of your application, even those not directly connected in the usual parent-child structure.

The useContext Hook itself doesn't provide mechanisms for updating the state. It only allows you to read a context's current value. The actual state updates are typically handled by the context provider, which often uses useState or useReducer internally.

Example

import { createContext, useContext } from "react";
const Context = createContext();
const Child = () => {
const context = useContext(Context);
return <div>{context.data}</div>;
};
//Top most component
const App = () => {
return (
<Context.Provider value={{ data: "Data from context!" }}>
<Child />
</Context.Provider>
);
};

In the snippets above, we created a Context object (i.e., the central data store) using the createContext() function. This object can be accessed from anywhere in your application and provides two key components: Provider and Consumer (or the useContext HHook).

The Provider component, positioned higher up in your component tree, acts as a higher-order component, passing down its value prop to any nested components that need access. On the other hand, the Consumer component or the useContext Hook allows child components to tap into this shared data.

When the Provider's value changes, any component using useContext automatically re-renders to reflect the updated information, ensuring a consistent and synchronized user interface.

A significant advantage of useContext is that it eliminates the need for prop drilling, leading to cleaner, more maintainable code. But be careful not to overuse it, as frequent updates with a lot of data can slow down your app.

If your context value is complex or changes frequently, consider using memoization techniques like useMemo to avoid unnecessary re-rendering of consumer components. Let's now explore the useRef Hook. Even though it's not directly tied to context, it often partners with useContext.

#The useRef Hook

The useRef Hook provides a way to access and interact with DOM nodes or store a mutable value that does not cause a re-render when updated. It creates a "box" that can hold a value, which remains unchanged even if the component re-renders multiple times.

Example

import { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}

The inputRef object is initialized by calling useRef(initialValue). The initialValue can be any JavaScript value, including null, objects, arrays, or functions. The ref object has a single property called .current, which holds the stored value. You can access and modify this value directly without triggering a re-render of your component.

The useRef Hook provides a way to directly access and manipulate DOM elements within functional components. It also stores values that persist across renders, which helps track input focus, timers, counters, or previous prop values.

When you want changes to be reflected in the UI, you need to update the ref's .current value manually. Changes to this value won't automatically trigger re-renders like changes to any state managed with useState.

The useRef Hook isn't meant for managing UI state that triggers re-renders. Still, it's a valuable tool for storing persistent values or interacting directly with elements in your webpage.

Now, let's discuss the useId Hook, a simple yet powerful tool for enhancing accessibility in React applications.

#The useId Hook

The Hook used is a utility for generating unique IDs that are stable across the server and the client renders. This is particularly important for building accessible web applications that rely on labels and relationships between elements, as it ensures consistent IDs for associating elements like labels and form inputs.

How it works:

  • The Hook generates a unique ID string when a useId component is first rendered on the server.
  • The ID is then sent to the client as part of the HTML, and React reuses the same ID during hydration, ensuring consistency between server and client renders.
  • Each useId call within a component generates a unique ID. These IDs remain stable across re-renders and updates unless the component is unmounted and remounted.

Example

import { useId } from 'react';
function MyComponent() {
const inputId = useId(); // Generate a unique ID
const labelId = useId();
return (
<div>
<label htmlFor={inputId}>My Input:</label>
<input id={inputId} type="text" />
<p aria-describedby={labelId}>This is a description for the input field.</p>
</div>
);
}

In this example, the useId Hook generates unique IDs for the input and label elements, ensuring a correct association between them for accessibility purposes.

The useId simplifies creating components by generating unique IDs, essential for associating labels with React form inputs and other interactive elements. However, it's limited to React 18 and later versions and should not be used to generate keys for dynamic lists. Let’s discuss the useDeferredValue Hook next.

#The useDeferredValue Hook

The useDeferredValue Hook is a performance optimization tool for deferring updating a value or re-rendering certain parts of your UI until the browser has time to render the updates.

How it works:

It takes two arguments: the value you want to defer (it can be of any type) and an optional timeout in milliseconds. React prioritizes rendering other parts of the UI that depend on the non-deferred value. This means that if the non-deferred value changes, React will immediately update those parts, while the parts that rely on the deferred value will update later in a less urgent manner.

Example

import { useState, useDeferredValue } from 'react';
function MyComponent() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ExpensiveList items={deferredText} />
</div>
);
}

In this example, ExpensiveList depends on deferredText. When you type something, the input box immediately shows what you typed. But, the ExpensiveList, which might take longer to update, waits a moment before it shows the new text. This keeps the input box snappy and responsive while the slower part catches up in the background.

By deferring the re-rendering of expensive components, you can make your UI feel more responsive and avoid UI blocking. A user can interact with the UI elements (like typing in an input field) without experiencing lag, even while heavy computations or rendering occurs in the background.

The useDeferredValue is particularly effective for optimizing common UI patterns like:

  • Search Input with Results List.
  • Large Lists or Tables.
  • Complex Calculations.

The useDeferredValue might not be very helpful if the problem you are trying to solve is not related to slow rendering. Overusing it can lead to users being served stale data.

#The useDebugValue Hook

The useDebugValue Hook is primarily a developer tool in React. It's designed to enhance the debugging experience when working with custom Hooks. It allows you to display a label for your custom Hook in React DevTools, providing additional information about its state or value.

Here is how it works:

The useDebugValue is usually called inside your custom Hook, usually at the top level. It takes two arguments: value and formatter (optional).

  • value: The value you want to display in React DevTools. This can be the current state of your Hook, a calculated value, or any relevant information for debugging.
  • formatter: A function that takes the value and returns a formatted string. This allows you to customize how the value is displayed in DevTools.

    Example

import { useState, useDebugValue } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
useDebugValue(count); // Display 'count' in DevTools
// ... (rest of your custom Hook logic)
}

The useDebugValue helps developers gain more insight into the inner workings of their custom Hooks during development. It enhances the debugging experience in React DevTools, making it easier to identify and resolve state and data flow issues within custom Hooks.

The useDebugValue shines when you use multiple custom Hooks within a component; it helps you distinguish between them in DevTools. However, it doesn't change how your Hooks work, as it's purely a debugging tool. It only applies during development and doesn't affect the production build of your application. The following Hook we'll discuss is the useImperativeHandle Hook.

#The useImperativeHandle Hook

The useImperativeHandle is a React Hook that allows you to customize the instance value exposed to parent components when using ref.

To use useImperativeHandle, first, your child component must be wrapped in the forwardRef function. This allows the child component to receive a ref object from its parent. In the child component, call the useImperativeHandle with the following arguments:

  • ref: The ref object passed down from the parent.
  • createHandle: A function that returns an object containing the methods or values you want to expose to the parent.
  • dependencies (optional): An array of dependencies that trigger the createHandle function to re-execute if they change.

Example

import { forwardRef, useRef, useImperativeHandle } from 'react';
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focusInput: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} />;
});
function ParentComponent() {
const childRef = useRef(null);
const handleClick = () => {
childRef.current.focusInput();
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>Focus Child Input</button>
</div>
);
}

In this example, the ParentComponent can call focusInput on the ChildComponent instance through the ref.

Using the useImperativeHandle Hook, you can create controlled components where the parent can manage the child's state or behaviour. Instead of exposing the entire child component instance to the parent, you can expose only the necessary methods or properties.

The useImperativeHandle Hook shines in creating custom form inputs with validation logic exposed to the parent component or controlling video or audio playback from a parent component.

While useImperativeHandle offers robust control over child component behavior, it adds complexity that may be unnecessary for simple interactions, as props and callbacks typically suffice for most parent-child communication.

#The useLayoutEffect Hook

The useLayoutEffect is a React Hook that is very similar to useEffect but with a crucial difference in timing. While useEffect runs after the browser has painted the screen (i.e., after your component's changes are visually reflected), useLayoutEffect runs synchronously right before the browser paints.

How it works:

  • First, the component renders with the initial state and props.
  • React then updates the actual DOM based on the render.
  • The useLayoutEffect Hook runs immediately after DOM changes but before the browser displays them.
  • The Hook's code executes synchronously, allowing DOM measurements and manipulations.
  • The browser then paints the updated UI, reflecting the changes made by useLayoutEffect.

Example

import { useState, useLayoutEffect } from 'react';
function MyComponent() {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const myRef = useRef(null);
useLayoutEffect(() => {
const element = myRef.current;
if (element) {
setDimensions({
width: element.offsetWidth,
height: element.offsetHeight
});
}
}, []); // Empty dependency array ensures it runs only once after initial render
return (
<div ref={myRef}>
This div's dimensions are: {dimensions.width} x {dimensions.height}
</div>
);
}

The useLayoutEffect allows you to read the layout from the DOM and make immediate changes in the next paint. This is crucial for scenarios like:

  • Measuring the dimensions of a component after it's rendered.
  • Adjusting the position or styling of an element based on its size or location.
  • Building canvases and UIs that require complete control of the layout.

It is best used to calculate dimensions and positions, perform other layout-related operations, and synchronize animations with the DOM layout to avoid visual inconsistencies.

Because the useLayoutEffect Hook runs synchronously, it can block the browser's rendering pipeline if the effect is computationally expensive. This can lead to a less responsive user experience. Let's discuss the useInsertionEffect Hook next.

#The useInsertionEffect Hook

The useInsertionEffect is a React Hook also introduced in React 18. It's primarily designed for library authors working with CSS-in-JS libraries. It allows you to inject styles or other DOM nodes into the document before layout calculations occur.

Here's how it works:

The Hook runs immediately after a component is mounted into the DOM before the browser performs layout calculations. This ensures that styles are applied before other effects (like useLayoutEffect or useEffect that might depend on those styles).

Example

import { useInsertionEffect } from 'react';
useInsertionEffect(() => {
// Code to inject styles or DOM nodes
const styleElement = document.createElement('style');
document.head.appendChild(styleElement);
return () => {
// Cleanup function (optional) to remove the injected nodes
document.head.removeChild(styleElement);
};
}, [dependencies]); // Optional dependency array

Similar to useLayoutEffect, useInsertionEffect executes synchronously. As a result, it shouldn't be used for asynchronous operations. The useInsertionEffect Hook is designed to optimize the performance of CSS-in-JS libraries. Injecting styles before the browser calculates layouts prevents unnecessary and costly recalculations to ensure a smooth user experience.

While not typically required for everyday app development, it's a valuable tool for library authors seeking to enhance their CSS-in-JS solutions.

#The useSyncExternalStore

The useSyncExternalStore Hook is designed for a specific purpose: subscribing to external data sources and efficiently synchronizing their updates with your React components. These external sources can be anything from browser local storage to a Redux store or even a custom state management solution.

The useSyncExternalStore subscribes to an external store and retrieves its current state. It then re-renders your component whenever the store's state changes, ensuring your component displays the latest data. The subscription is cleaned up when the component is removed to prevent memory leaks.

Here's an example implementation:

import { useSyncExternalStore } from 'react';
function MyComponent() {
const storeState = useSyncExternalStore(
store.subscribe, // Subscribe to the store
store.getSnapshot, // Get the current snapshot
);
return <div>{storeState}</div>;
}

The useSyncExternalStore provides a way to integrate with various external stores, abstracting away the complexities of subscription management. It also works with React's concurrent features, ensuring smooth rendering and responsiveness even with complex updates from the external store.

The useSyncExternalStore shines in synchronizing browser storage (i.e., keeping your component's state in sync with localStorage or sessionStorage) and subscribing to external data streams like WebSockets or server-sent events. Let's discuss the useTransition Hook next.

#The useTransition Hook

The useTransition Hook improves the user experience when dealing with state updates that could cause your UI to become unresponsive or slow. It allows you to mark specific state updates as "transitions," which signals to React that these updates are less urgent and can be interrupted if necessary.

How it works

  • The useTransition returns isPending (boolean) to show if a transition is happening and startTransition (function) to mark updates as transitions.
  • React prioritizes urgent updates over transitions, ensuring a smooth user experience.

Example

import { useState, useTransition } from 'react';
function MyComponent() {
const [isPending, startTransition] = useTransition();
const [value, setValue] = useState('');
const handleChange = (event) => {
startTransition(() => {
setValue(event.target.value);
});
};
return (
<div>
<input type="text" value={value} onChange={handleChange} />
{isPending ? <div>Loading...</div> : <HeavyComponent value={value} />}
</div>
);
}

In this example, updating the value state is marked as a transition. If the user types quickly into the input, the input will remain responsive, and the HeavyComponent will only update after a slight delay, this avoids rendering substandard UI.

The useTransition makes your app feel smoother and more responsive, especially during complex updates. It prioritizes essential changes, like user input, and handles less urgent updates in the background without you needing to write extra code.

The useTransition Hook optimizes specific scenarios within React 18+ applications, such as search inputs, extensive lists, and complex calculations where immediate UI updates aren't essential. However, it's important to use it judiciously, as it's not suitable for urgent updates and can introduce unnecessary complexity if overused.

Beyond the established Hooks, React boasts a set of experimental Hooks, offering a glimpse into the framework's future. These Hooks are still in development and might change, so they're not recommended for production use yet. However, they provide key features, like optimistic UI updates (showing changes before they're confirmed) and handling actions with built-in state management (useOptimistic and useActionState, respectively).

#The useOptimistic Hook

The useOptimistic is designed to enhance the perceived responsiveness of your UI by allowing you to optimistically update the UI before an asynchronous action (like a network request) is complete. This means the user sees the expected outcome immediately, even though the action is still in progress. React will revert the UI to its previous state if the action fails.

How it works:

  • The useOptimistic takes an action function and an initial optimistic value.
  • It returns the current value, a pending status indicator, and a reset function.
  • The current value starts as optimistic, changes during the action, and updates with the actual result.

Example

import { useState, useOptimistic } from 'react';
function MyComponent() {
const [comments, setComments] = useState([]);
const [newComment, addNewComment] = useOptimistic(
(text) => fetch('/api/comments', { method: 'POST', body: text }),
'' // Initial optimistic value is an empty string
);
const handleSubmit = async (event) => {
event.preventDefault();
await addNewComment(newComment); // Trigger the action
setComments([...comments, newComment]); // Update comments list
setNewComment(''); // Clear the input field
};
// ...
}

#The useActionState Hook

The useActionState is often used in conjunction with useOptimistic. It simplifies the management of the state associated with an asynchronous action. It automatically tracks the action's status (pending, success, error) and provides convenient methods for handling its result.

How it works:

  • First, you must provide an action function to useActionState to handle the asynchronous operation.
  • The useActionState returns the action's current state (idle, pending, success, or error), a function to trigger the action (dispatch), and a function to reset the state (reset).
  • The returned state object contains the action's status, returned value, and potential errors.

Example

import { useActionState } from 'react';
function MyComponent() {
const [commentState, addComment] = useActionState(
(text) => fetch('/api/comments', { method: 'POST', body: text })
);
// ...
}

The useOptimistic and useActionState Hooks work together to enhance user interactions. Optimistically, updating the UI before an action is complete gives the user a more responsive experience.

Additionally, useActionState simplifies the handling of state associated with asynchronous actions, such as network requests. It automatically tracks the action's status (pending, success, error), reducing the need for manual state updates and providing built-in error management.

It's important to know that these experimental Hooks are still under development, may introduce complexity, and their behavior could change in future React versions, so use them with caution.

#Conclusion

React Hooks have revolutionized functional component development, offering a straightforward way to manage state, side effects, and other crucial aspects. From basic state updates with useState to optimizing performance with useMemo and useCallback, these Hooks empower developers to build more efficient, maintainable, and interactive applications. While experimental Hooks like useOptimistic and useActionState are still evolving, they hint at an even brighter future for React development, promising enhanced user experiences and streamlined workflows.

Ready to explore React Hooks' full potential? Sign up for your free developer account and join the Hygraph developer community, where you'll find extensive resources, tutorials, and discussions to help you master these powerful tools. Stay curious, experiment with new Hooks, and unlock the full potential of React in your projects.

Blog Author

Chidi Eze

Chidi Eze

Technical writer

Chidi is a software engineer and technical writer with experience in building user-friendly applications and creating content around composable architectures.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.