๐Ÿš€Building Accessible Forms in Next.js with Server Actions ๐Ÿคฏ

๐Ÿš€Building Accessible Forms in Next.js with Server Actions ๐Ÿคฏ

Add Forms to Next.js the Right Way with Server Actions

ยท

4 min read

Hey everyone, it's your friend TAQUI here! In this article, we will look at how to build effective and accessible forms in Next.js applications using Server Actions. We will cover handling form submissions, data mutations, loading states, errors, and more - directly within React components for a unified and seamless experience.

Intro to Forms in Next.js

Traditionally in Next.js, forms were handled by creating separate API routes to handle submissions on the server-side securely. This required making additional requests to the backend after submitting a form.

While this works, it has some downsides:

  • Multiple network requests are needed - one for submission, another for the response

  • Form handling is split between the component and API route files

  • It's not straightforward to integrate loading/error states

Server Actions were introduced to address these issues by allowing data operations and business logic to be directly integrated into components.

Server Actions Overview

Server Actions allow defining functions that securely run only on the server-side. These can be called directly from React components using the action prop on forms.

Some key capabilities include:

CapabilityDescription
Data ValidationValidate form data against schemas before handling submission
Data OperationsPerform CRUD actions like inserts, updates, deletes directly in database
Error HandlingCatch errors from data layer and surface in UI
RevalidationCall revalidatePath() to refresh cached data/UI after mutations

By handling everything in the component, forms can provide a unified progressive enhancement experience whether JavaScript is enabled or not.

The Example App

To demonstrate these techniques, we will build a simple Todo list app that allows adding and deleting items.

The core pieces are:

  • Displaying existing todos from cached data

  • An "Add Todo" form component

  • A "Delete Todo" form for each todo

  • Loading/error states with React hooks

  • Ensuring accessibility

Let's dive into each part!

Displaying Todos ๐Ÿ“ƒ

We render the initial todos data from the server component:

// pages/index.js

export async function getServerSideProps() {

  const todos = await fetchTodos()

  return {
    props: {
      todos
    }
  }
}

function Home({todos}) {

  return (
    <ul>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />  
      ))}
    </ul>
  )
}

Adding Todos

The Add Todo form component renders an input and submit button. It specifies the server action handler:

<form action={createTodo}>

  <input />

  <button>
    Add Todo
  </button>

</form>

The createTodo action imports Zod to validate the data schema:

// serverActions.js

import { z } from 'zod'

const schema = z.object({
  title: z.string()
})

export async function createTodo(req, res) {

  try {

    await schema.parseAsync(req.body)

    // insert into db
    await db.todo.create(req.body)

    return res.send({msg: 'Todo added'})

  } catch (err) {

    return res.send({error: err.message})

  }

}

On success, it calls revalidatePath() to refresh the page cache. Now submissions are handled securely on the server without separate requests.

Deleting Todos

Deleting follows a similar flow. Each todo item renders a delete form passing the ID:

function TodoItem({todo}) {

  return (
    <li>
      {todo.title}

      <form action={deleteTodo}>
        <input type="hidden" value={todo.id} />

        <button>Delete</button>  
      </form>
    </li>
  )
}

The deleteTodo action removes it from the database and revalidates.

Loading and Error States

The useFormStatus hook from React exposes pending state to toggle loading styles:

// SubmitButton.js

function SubmitButton() {

  const {pending} = useFormStatus()

  return <button disabled={pending}>Submit</button>

}

Errors are surfaced by returning messages from failed server actions.

Accessible Design Patterns

Some strategies to ensure forms are accessible include:

  • Labeling all inputs

  • Announcing success/errors to screen readers

  • Avoiding interruptions with a "live region"

  • Using ARIA attributes like aria-disabled

See the full example on GitHub for the implementation.

Conclusion

By integrating forms fully into components using Server Actions, Next.js provides a clear and unified approach. Submissions are handled securely and directly in React without separate requests. This simplifies loading states, errors and overall UX.

With some best practices around validation, navigation and accessibility - Server Actions make building effective forms straightforward and progressive.

๐Ÿ’ก If you find this article helpful then don't forgot follow me in Github and Twitter .

๐Ÿ’ก
SUBSCRIBE ๐Ÿ“ฉ NEWSLETTER FOR MORE !

Like ๐Ÿ’–

Share๐Ÿ”—

Follow me in Hashnode โœ…

Did you find this article valuable?

Support Today'sCode by becoming a sponsor. Any amount is appreciated!

ย