Mastering forms in Next.js 15 and React 19

Creating forms in modern React has evolved significantly with the latest releases. In this post we’ll explore best practices for building modern forms with the newest primitives and APIs.
First, it’s important that you understand best practices for creating forms in the previous generation of React. Much of the same rules apply today such as using the native features when possible and not reaching for controlled components by default.
🚧 Checkout the code for the completed examples. This post will include snippets, but the repo contains the full runnable examples.
Here’s what we’ll be exploring
- Server Actions: asynchronous functions that are executed on the server.
- useActionState: a Hook that allows you to update state based on the result of a form action.
- useOptimistic: optimistically update your UI after submitting a form.
- revalidateTag: purge cached data after a mutation.
- Form: a Next.js component that provides prefetching, client-side navigation on submission, and progressive enhancement.
Server Actions
A Server Action is an asynchronous function that runs on a server. They can be called in Server and Client Components to handle form submissions and data mutations.
Here’s a basic example of a Server Action.

And this is how we would use that Server Action.

Our submitComment
Server Action can be imported directly into our Client Component and passed as the action
for the form
element. When the form is submitted it calls the function and provides the formData
as a param. This Server Action is performing a mutation by creating a record in a database. After a mutation you’ll typically need to purge the cache of previously cached requests, which we can do by calling revalidateTag
.
useActionState
Now let’s expand on this and build a more complex form. This form will include data validation, error states, and pending states. We can facilitate all of these with React’s useActionState
hook.

Notice that instead of providing the Server Action to form element, we provide it to the useActionState
hook. It also accepts initial values, which this form doesn’t include, so we pass null
.
The state
value returned from the hook is the data that is returned from our Server Action, and the hook provides a pending
state which we can use to disable our button while the Server Action is executing.

In this example the Server Action includes validation via Zod. If the data provided doesn’t pass Zod’s validation it’ll provide errors which we can return to client. These become available via the state
value, and we can render an error message by accessing that state: {state?.errors?.name && <p aria-live=”polite”>{state.errors.name}</p>}
You’ll also notice that we return the values provided to the Server Action. By default, our form is reset after submitting it. To prevent this, we add a defaultState
to our inputs, and set it to the values returned from the Server Action defaultValue={state?.values?.name ?? ‘’}
.
useOptimistic
Optimistic Updates is a technique used to improve the user experience by immediately showing UI updates to the user, rather than waiting for the Server State to be updated and cache revalidated. React now provides a hook to make Optimistic Updates much easier.

We start by providing the hook with the current state. In this case, a list of comments. The hook provides a way to get the optimistic value, and a way to update it. We use optimisticComments
to render our list of comments. After submitting the form we call addOptimisticComment
and provide it with our new comment. Immediately, the UI is updated to show the new comment in the list. Meanwhile, we’ve called our Server Action to update the Server State and revlidate the cache. If an error was to occur while running our Server Action, the UI would fallback and undo the optimistic update.
Form
The Form component provide by Next.js is a handy way to prefetch data, update Search Params in the URL, or provide progress enhancements. In this example, we’ll use it to facilitate searching through a list of comments by updating a Search Param in the URL.

First, in our Server Component, we use a Search Param named query
when fetching our comments.

In our UI component we use Next.js’ Form component and provide it the path that we want to prefetch. In this example we are on the search route already so it will refetch the data for the page. The name
provided to the input will become the name of the Search Param that is updated, and the value of the input will be the Search Param value. When the form is submitted it will use the Search Param when fetching the comments to return results that match our query.
Conclusion
Building forms in Next.js and React has evolved significantly with the introduction of Server Actions, the useActionState
and useOptimistic
hooks, and Next.js’ dedicated Form
component. These features enable more efficient data handling, streamlined validation, and seamless user experiences. By leveraging these APIs, you can craft forms that are both robust and user-friendly—without adding unnecessary complexity to your codebase.
Udacity is currently looking for skilled React developers to help us build talent transformation tools. If you’re skilled in developing international UIs at scale then reach out to me on Twitter @kolbysisk.