Using Next.js server actions with React Hook Form
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:
- Node.js and npm (Node Package Manager)
- Next.js 13.4 or later
Project Setup
Let’s set up our Next.js project and install the necessary dependencies.
- 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 🚀