Explore new hooks coming up in React 19

The React 19 RC was released on April 25, 2024. It introduces a range of new features set to transform React development. Contrary to popular belief, the React team isn’t only focused on React Server Components and Next.js. The upcoming release emphasizes web primitives and form data being one of those primitives.

Consider this simple example where we are using the useState hook to manage isPending and data, message. It works fine but there is quite a lot of boilerplate to handle data, isPending, and message states.

export default function App() {
const [name, setName] = useState('');
const [message, setMessage] = useState(null);
const [isPending, setIsPending] = useState(false);

const submitAction = async () => {
setIsPending(true);
const data = await changeName(name);
setIsPending(false);
setMessage(data.message);
};

return (

<form action={submitAction}>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button type="submit" disabled={isPending}>
{isPending ? "Updating" : "Update"}
</button>
<p>{message}</p>
</form>
);
}

React 19 has support for using async functions in transitions to handle pending states, errors, forms, and optimistic updates automatically.

In React 19, functions that trigger transitions are now called actions. So action is a function that triggers a transition which we can do in multiple ways. We can create an action using useActionState. It makes easy-to-use actions and also gets status updates on that transition that the action triggers and then sets state based on the returned data.

New client-side hooks coming up in React 19 addresses significant issues related to data mutations and form management, simplifying the handling of pending states, errors, optimistic updates, and sequential requests, which previously required manual intervention.

Let’s look into these new hooks in detail.

1. useActionState hook

useActionState is a hook that a more streamlined way to handle form actions.

useActionState accepts 3 argument

  • fn: The function to be called when the form is submitted or the button pressed
  • initialState: Initial state
  • permalink (optional): A string containing the unique page URL that this form modifies.

It returns

  • initial state/ or the data returned when the actions is completed
  • Action function
  • pending state indicator
export default function App() {
const [data, submitAction, isPending] = useActionState(
async (previousState, formData) => {
return await changeName(formData.get("name"));
},
null
);

return (

<form action={submitAction}>
<input type="text" name="name" />
<button type="submit" disabled={isPending}>
{isPending ? "Updating" : "Update"}
</button>
<p>{data?.message}</p>
</form>
);
}

// action.js
export async function changeName(name) {
if (name) {
return { success: true, message: "Name updated" };
} else {
return { success: false, message: "Name cannot be blank" };
}
}

Using useActionState, we no longer need a separate data state because it is returned by the hook. The submit action, which we provide to the hook, receives the current state and form data as arguments by default.

This means we don’t have to manually set the user’s input state; instead, we can directly retrieve it from the form data. Additionally, the hook provides an isPending state. The behavior remains consistent with less boilerplate code.

NOTE: In earlier React Canary versions, this API was part of React DOM and called useFormState.

2. useFormStatus hook

Building design systems often involves components that rely on data from the surrounding <form>. Traditionally, this data is passed down through props, potentially creating a long chain of prop drilling or Context.

To simplify this common scenario, React 19 introduces the new useFormStatus hook. The useFormStatus hook provides an easy way to track the status of form submissions.

It does not take any parameters and returns a status object with the following properties:

  • pending: Status of form submission
  • data: Form data
  • method: HTTP method used for submission (e.g., “POST”)
  • action: A reference to the function passed to the action prop on the parent <form>

Things to note:

  • The useFormStatus hook must be called from a component that is rendered inside a <form>.
  • It will not return status information for any <form> rendered in that same component or children components.

In this example, useFormStatus is called in the child component of form, so the form status and data are available using the hook.

export default function App() {
const ref = useRef(null);
return (

<form ref={ref} action={async (formData) => {
await submitForm(formData);
ref.current.reset();
}}>
<UsernameForm />
</form>
);
}

export default function UsernameForm() {
const {pending, data} = useFormStatus();

return (

<div>
<h3>Request a Username: </h3>
<input type="text" name="username" disabled={pending}/>
<button type="submit" disabled={pending}>Submit</button>
<br />
<p>{data ? `Requesting ${data?.get("username")}...`: ''}</p>
</div>
);
}

When the useFormStatus hook is called inside the component with a form, pending is never tracked and it will always be false.

function Form() {
const { pending } = useFormStatus(); // pending will always be false
return <form action={submit}></form>;
}

Examples are taken from react.dev

3. useOptimistic hook

The useOptimistic hook lets us create a smoother user experience for forms. It allows us to update the interface with the expected outcome immediately after submission, even before the server confirms the change. This eliminates the wait for the server’s response, making the app feel more responsive.

Here’s how it works:

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

We provide the current state and a updateFn that merges the current state with an optimistic update. addOptimistic is called with the optimistic value whenever an action is initiated. React will call updateFn and render the optimistic state.

If the action succeeds, the optimistic state becomes the final state. If it fails, React automatically reverts to the original state. This provides a responsive UI without displaying incorrect data.

Here is an example from react.dev.

import { useOptimistic, useState, useRef } from "react";
import { deliverMessage } from "./actions.js";

function Thread({ messages, sendMessage }) {
const formRef = useRef();
async function formAction(formData) {
addOptimisticMessage(formData.get("message"));
formRef.current.reset();
await sendMessage(formData);
}
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true
}
]
);

return (
<>
{optimisticMessages.map((message, index) => (

<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}

export default function App() {
const [messages, setMessages] = useState([
{ text: "Hello there!", sending: false, key: 1 } ]);
async function sendMessage(formData) {
const sentMessage = await deliverMessage(formData.get("message"));
setMessages((messages) => [...messages, { text: sentMessage }]);
}
return <Thread messages={messages} sendMessage={sendMessage} />;
}

4. use hook

The use hook is used to read the value of a resource like a promise or context.

i. Reading promise with use

The use hook simplifies data fetching and working with promises. It allows us to use the result of a promise directly inside our component without extra boilerplate.

const user = use(fetchUser());

The use hook takes a promise and returns its result. The use hook integrates with Suspense and error boundaries.

When we use the use hook, our component will pause until the provided Promise finishes. If our component is wrapped in a Suspense component, we’ll see a fallback UI while waiting. Once the Promise resolves successfully, the fallback disappears, and the data will be rendered. However, if the Promise is rejected, the closest Error Boundary’s fallback will be displayed instead.

NOTE: Unlike React Hooks, use can be called within loops and conditional statements.

const user = use(fetchUser());

let permissions = null;
if (user.role === 'admin') {
permissions = use(fetchUserPermissions(user.id));
}

ii. Reading context with use

When a context is passed to use, it works similarly to useContext. While useContext must be called at the top level of our component, use can be called inside conditionals like if and loops like for. use is preferred over useContext because it is more flexible.

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

function Sidebar({ children }) {
const theme = use(ThemeContext); //dark
const className = 'sidebar-' + theme;
return (

<section className={className}>
<h1>Sidebar</h1>
{children}
</section>
)
}

Conclusion

React 19 supercharges functional components with new hooks!

These hooks streamline common tasks, making development faster and more enjoyable.

  • Smoother User Experience: The useOptimistic hook lets us update the UI immediately after a user interacts with a form, even before data is confirmed by the server. This provides instant feedback and keeps users engaged.
  • Effortless Form Handling: The useFormStatus and useActionState hooks simplify form management. They track submission status and centralize form state, reducing boilerplate code.
  • Declarative Data Fetching: The use hook integrates with promises and Suspense. This allows us to fetch data cleanly and concisely, minimizing the need for extra code.

Overall, these new hooks benefit developers by:

  • Saving Time: They streamline common tasks, reducing development time.
  • Promoting Code Reusability: The focus on centralized state management encourages code reuse.
  • Building Better UIs: Faster development and a focus on user experience lead to more performant and engaging applications.

Need help on your Ruby on Rails or React project?

Join Our Newsletter