Advanced React Hooks Explained With Real World Code Examples: Part 1
Since the release of React 16.8 in 2019, building a reusable piece of UI (components) has become super easy. Thanks to the introduction of React Hooks. Now we don't write verbose class-based components anymore. Over the years, the React team has introduced a ton of built-in hooks that solve specific problems. But most of the people only stick with just useState() and useEffect(). But there are a lot of hooks that can improve your understanding and ability to write efficient code. As an experienced developer of React, I have tried to explain each of the advanced & newly introduced hooks with proper real-world code examples. Let's build the best app together. 1. useActionState Whenever you submit a form in React, you constantly juggle from to write the load of useStates and useEffects with fetch. With the introduction of React Server components, the useActionState made the process much easier. This hook requires two parameters. One is an action parameter, and the other is the initialState. It returns the updated state, formAction, and isPending status. let's break them down: const [state, formAction, isPending] = useActionState(myServerAction, initialState); Example: Let's say you want to build a form that sends a post request about a value in the database. You prefer to use the latest React RSC-based architecture (or you may use NextJS). Your form will catch real-time errors and send data to the DB. Here is the whole code example: import { useActionState } from "react"; // A server action // NOTE: prevState is required for the useActionState hook async function sendName(prevState: any, formData: FormData) { const name = formData.get("name"); if (!name) { return { error: "Name is required!" }; } // Imagine saving to database here return saveTODatabase(name); } //component export default function ContactForm() { const initialState = { error: "" }; const [state, formAction, isPending] = useActionState(sendName, initialState); return ( {isPending ? "Sending..." : "Submit"} {state.error && state.message} ); } Let's break this down: The useActionState() hook got two parameters A server action sendName() function that saves the name if the name input is typed. an initialState object that holds the error data. The useActionState() hook returns The updated state that contains the updated error data. The formAction function is passed to the form to perform this action on submit. The isPending boolean to get to know the form's asynchronous status. These returned values together give flexibility to write efficient code and perform secure transactions through components and databases. 2. useFormStatus Another useful hook to get the proper information about a form's status. This hook returns 4 key pieces of information about a form. These are, pending (boolean). method (form submission method i.e., POST, PUT) data (the submitted form data) action (The server action function) // nested inside a form component const { data, action, method, pending } = useFormStatus(); NOTE: useFormStatus() must be used inside a component that is a direct or indirect child of a Example: The pending is the key property of this hook. It checks the form asynchronous status. Moreover, it is sometimes used with useActionState() hook that I discussed earlier. You want to build a form that gives a good-looking loading UI needs to get the submitted data to show the client // Submit button using useFormStatus function SubmitButton() { const { pending, data } = useFormStatus(); return ( {pending ? 'Submitting...' : 'Submit'} {pending && ( Submitting email: {data?.get('email') as string} )} ); } export default function AdvancedForm() { const [state, formAction, isPending] = useActionState(handleSubmit, { message: '' }); return ( {state.message && {state.message}} ); } Check that the SubmitButton component is inside a form containing component. Now the useFormStatus() will work seamlessly accompanied by useActionState() of the parent component. 3. useDeferredValue It is the hook related to the performance boost. It defers the update of a value, making the UI more responsive. This is handy when updating the state with big data, heavy rendering, or slow components. It can be analogous to debouncing to some people. But there are some changes between them. const deferredInput = useDeferredValue(input); Example You want to build a search component that filters through a huge amount of data, and whenever you search for something through the input, the big filtering comes as a bottleneck. It makes the UI unresponsive and makes the typing input slow. One way to fix this is to use debouncing. But React has a built-in hook useDeffere

Since the release of React 16.8 in 2019, building a reusable piece of UI (components) has become super easy. Thanks to the introduction of React Hooks. Now we don't write verbose class-based components anymore. Over the years, the React team has introduced a ton of built-in hooks that solve specific problems.
But most of the people only stick with just useState()
and useEffect()
. But there are a lot of hooks that can improve your understanding and ability to write efficient code. As an experienced developer of React, I have tried to explain each of the advanced & newly introduced hooks with proper real-world code examples. Let's build the best app together.
1. useActionState
Whenever you submit a form in React, you constantly juggle from to write the load of useStates
and useEffects
with fetch
.
With the introduction of React Server components, the useActionState
made the process much easier.
This hook requires two parameters. One is an action parameter, and the other is the initialState. It returns the updated state
, formAction
, and isPending
status. let's break them down:
const [state, formAction, isPending] = useActionState(myServerAction, initialState);
Example:
Let's say you want to build a form that sends a post request about a value in the database. You prefer to use the latest React RSC-based architecture (or you may use NextJS). Your form will catch real-time errors and send data to the DB.
Here is the whole code example:
import { useActionState } from "react";
// A server action
// NOTE: prevState is required for the useActionState hook
async function sendName(prevState: any, formData: FormData) {
const name = formData.get("name");
if (!name) {
return { error: "Name is required!" };
}
// Imagine saving to database here
return saveTODatabase(name);
}
//component
export default function ContactForm() {
const initialState = { error: "" };
const [state, formAction, isPending] = useActionState(sendName, initialState);
return (
<form action={formAction}>
<input name="name" placeholder="Enter your name" disabled={isPending} />
<button type="submit" disabled={isPending}>
{isPending ? "Sending..." : "Submit"}
</button>
<p className="text-red-600">{state.error && state.message}</p>
</form>
);
}
Let's break this down:
- The
useActionState()
hook got two parameters- A server action
sendName()
function that saves the name if the name input is typed. - an
initialState
object that holds the error data.
- A server action
- The
useActionState()
hook returns- The updated
state
that contains the updated error data. - The
formAction
function is passed to the form to perform this action on submit. - The
isPending
boolean to get to know the form's asynchronous status. These returned values together give flexibility to write efficient code and perform secure transactions through components and databases.
- The updated
2. useFormStatus
Another useful hook to get the proper information about a form's status. This hook returns 4 key pieces of information about a form. These are,
-
pending
(boolean). -
method
(form submission method i.e., POST, PUT) -
data
(the submitted form data) -
action
(The server action function)
// nested inside a form component
const { data, action, method, pending } = useFormStatus();
NOTE: useFormStatus()
must be used inside a component that is a direct or indirect child of a
Example:
The pending
is the key property of this hook. It checks the form asynchronous status. Moreover, it is sometimes used with useActionState()
hook that I discussed earlier.
You want to build a form that gives a good-looking loading UI needs to get the submitted data to show the client
// Submit button using useFormStatus
function SubmitButton() {
const { pending, data } = useFormStatus();
return (
<div className="mt-2">
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
{pending && (
<div className="text-sm mt-1 text-gray-500">
<p>Submitting email: <strong>{data?.get('email') as string}</strong>p>
</div>
)}
</div>
);
}
export default function AdvancedForm() {
const [state, formAction, isPending] = useActionState(handleSubmit, { message: '' });
return (
<form action={formAction} method="POST" className="max-w-sm space-y-3">
<input
type="email"
name="email"
placeholder="Enter your email"
disabled={isPending}
/>
<SubmitButton />
{state.message && <p className="text-green-600 mt-2">{state.message}</p>}
</form>
);
}
Check that the SubmitButton
component is inside a form containing component. Now the useFormStatus()
will work seamlessly accompanied by useActionState()
of the parent component.
3. useDeferredValue
It is the hook related to the performance boost. It defers the update of a value, making the UI more responsive. This is handy when updating the state with big data, heavy rendering, or slow components. It can be analogous to debouncing to some people. But there are some changes between them.
const deferredInput = useDeferredValue(input);
Example
You want to build a search component that filters through a huge amount of data, and whenever you search for something through the input, the big filtering comes as a bottleneck. It makes the UI unresponsive and makes the typing input slow.
One way to fix this is to use debouncing. But React has a built-in hook useDefferedValue()
to solve this.
import { useDeferredValue, useState } from 'react';
// bigList is a huge list of data!
function SearchComponent({ bigList }) {
const [input, setInput] = useState('');
const deferredInput = useDeferredValue(input); // defer heavy filtering
const filteredList = bigList.filter(item =>
item.includes(deferredInput)
);
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Search..."
/>
<ul>
{filteredList.map((item, i) => <li key={i}>{item}</li>)}
</ul>
</div>
);
}
Now you can see the UI is responsive, as the input gets deferred inside the filteredList
function. It tells React that this deferred input has less priority than the real input. So the real input changes immediately.
4. useTransition
This is another performance hook and is often confused with the useDeferredValue hook. This hook provides a function that tells React that these codes are less prioritized tasks. In other words, this hook manages the state transitions of states that are non-urgent, like background updates or heavy sorting/filtering. This helps to make the UI responsive.
Example:
You want to create a search component that filters a large dataset based on the input value provided in an input field. The data comes from another source. Use the useTransition()
hook to make the filtering non-urgent. This will make the input more responsive
import { useState, useTransition } from "react";
import { data } from "./data";
export default function SearchComponent() {
const [items, setItems] = useState(data);
const [input, setInput] = useState("");
const [isPending, startTransition] = useTransition();
function handleInput(e) {
//this state change is most prioritized
setInput(e.target.value);
startTransition(() => {
//this tasks are non-urgent
const filtered = data.filter((item) => {
return item.name.toLowerCase().includes(e.target.value);
});
setItems(filtered);
});
}
return (
<div>
<input type="text" value={input} onChange={handleInput} name="" id="" />
{isPending && <h1>Loading...</h1>}
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
5. useDebugValue
This hook is not that much used. Just to let you know that this type of hook exists in React :). This hook is primarily used for custom hooks to help developers debug hook values. A state passed to this hook can be seen through the DevTools.
Example
Imagine you are building a chat app and currently building a custom hook that checks whether the user is online or offline.
To debug, you can use the useDebugValue hook to check the status through the devtools
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useDebugValue(isOnline ? "Online" : "Offline"); //