Using Next.js server actions with React Hook Form

danielmdob
4 min readOct 27, 2023

In this tutorial, we will create a simple name form in a Next.js application using React Hook Form for form handling and validation and sending the data of the form using Next.js server actions. By the end of this tutorial, we will have a fully functional name form sent to a server action.

Prerequisites

Before we begin, ensure you have the following installed:

  1. Node.js and npm (Node Package Manager)
  2. Next.js 13.4 or later

Project Setup

Let’s set up our Next.js project and install the necessary dependencies.

  1. Create a new Next.js project by running the following command:
npx create-next-app@latest name-form-app --eslint --app --src-dir
cd name-form-app

2. Install the required packages for form handling and validation:

npm install react-hook-form

3. Update next.config.js to enable server actions (currently experimental).

/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
}

module.exports = nextConfig

Creating shared types

A nice benefit of server actions is being able to share types between your backend and frontend. Create a new file src/types.tx with the following content:

type FullName = {
firstName: string;
lastName: string;
};

Creating the Form

In this example, we’ll create a name form that takes a first name and last name as input fields.

Create a new file called NameForm.tsx in the src/app/components directory with the following content:

'use client'

import { useForm } from 'react-hook-form';
import React from 'react';

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ type, ...props }, ref) => {
return (
<input
type={type}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"

export const NameForm = () => {
const { register } = useForm<FullName>()
return <form className="flex flex-col items-center space-y-2">
<Input placeholder="First Name" {...register('firstName')} />
<Input placeholder="Last Name" {...register('lastName')} />

<button type="button" className="text-white bg-blue-700 font-medium rounded-lg text-sm px-5 py-2.5">Confirm</button>
</form>
}

Creating the Server Action

In this example we will write an action for logging the full name based on the first and last name.

Create a new file app/actions.ts that logs the full name and returns what was logged:

'use server'

export async function getFullName({firstName, lastName}: FullName) {
const loggedContent = `entered name: ${firstName} ${lastName}`;
console.log(loggedContent);
return loggedContent;
}

This code would run server-side. So it would be a good opportunity to perform database operations and similar procedures. This one was written like this for simplicity.

Use the Server Action

Now, we can use the server action in our NameForm component. The contents of the file would change to this:

'use client'

import { useForm } from 'react-hook-form';
import React, { useState } from 'react';
import { getFullName } from '@/app/actions';

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({type, ...props}, ref) => {
return (
<input
type={type}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"

export const NameForm = () => {
const [serverResponse, setServerResponse] = useState<string | null>(null)
const {register, handleSubmit} = useForm<FullName>();
const action: () => void = handleSubmit(async (data) => {
const response = await getFullName(data);
setServerResponse(response);
});
return <>
<form action={action} className="flex flex-col items-center space-y-2">
<Input placeholder="First Name" {...register('firstName')} />
<Input placeholder="Last Name" {...register('lastName')} />

<button className="text-white bg-blue-700 font-medium rounded-lg text-sm px-5 py-2.5" type="submit">
Confirm
</button>
</form>
Server response: {serverResponse}
</>
}

Important things to notice:

  • This way of defining the action allows the data to implicitly have the type used in the react-hook-form generic. FullName , in this example.
  • Server output is used client-side which is a usual need.

To test the form, you can run npm run dev . Additionally, the code built to test this example was uploaded to:

Summary

In this tutorial we learned:

  • How to set up a form using react-hook-form
  • Send it to a Next.js server action
  • Write a server action
  • Use the action’s output in the client

Thanks for your time, and keep coding with a smile 🚀

--

--

danielmdob

Software engineer from Costa Rica. A tech enthusiast that loves full-stack Typescript setups.