Next.js Actions Tutorial: A Step-by-Step Guide

Server Actions are asynchronous functions defined in server components that can be invoked from client components.

Next.js Actions Tutorial: A Step-by-Step Guide
Next.js Actions Tutorial: A Step-by-Step Guide - mobilelabs.in

Next.js, introduced Server Actions in version 14, enabling developers to execute server-side code seamlessly without needing dedicated API routes. This feature enhances the efficiency of handling tasks like form submissions, data fetching, and interactions with databases directly from the client side.

What are Server Actions?

Server Actions are asynchronous functions defined in server components that can be invoked from client components. They allow for operations such as:

  • Form submissions
  • Data mutations
  • API calls
  • Database interactions

To enable Server Actions in your Next.js project, you need to configure your next.config.js 

module.exports = {
  experimental: {
    serverActions: true,
  },
};

Creating a Simple Todo Application

To illustrate the use of Server Actions, let’s create a simple Todo application where users can add tasks via a form.

Step 1: Create the Form Component

First, create a form for adding new tasks. This will be a controlled component that submits data to our Server Action

// components/TodoForm.js
'use client';

import { useFormStatus } from 'react-dom';

const TodoForm = () => {
  const { pending } = useFormStatus();

  return (
    <form action={addTodo}>
      <div className="flex flex-col mb-4">
        <label htmlFor="new-todo" className="mb-2 text-lg font-medium">
          Add a new task
        </label>
        <input
          type="text"
          id="new-todo"
          name="new-todo"
          className="p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
          placeholder="Enter your task"
        />
      </div>
      <button
        type="submit"
        disabled={pending}
        className="w-full py-2 px-4 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none"
      >
        Add Task
      </button>
    </form>
  );
};

export default TodoForm;

Step 2: Implement the Server Action

Next, define the addTodo function that will handle the form submission on the server

// actions/addTodo.js
'use server';

export async function addTodo(formData) {
  const task = formData.get('new-todo');
  // Here you would typically interact with your database
  console.log(`New task added: ${task}`);
  
  // Simulate adding to a database
  await new Promise(resolve => setTimeout(resolve, 1000)); // Simulating async DB operation
  
  return { success: true };
}

Step 3: Integrate the Form and Action

// pages/index.js
import TodoForm from '../components/TodoForm';
import { addTodo } from '../actions/addTodo';

export default function Home() {
  return (
    <div>
      <h1 className="text-2xl font-bold">Todo List</h1>
      <TodoForm />
    </div>
  );
}

Enhancing User Experience with Loading States

Using the useFormStatus hook allows you to manage loading states effectively. The submit button is disabled while the form is being processed, providing feedback to users.

import React from "react";
import { useFormStatus } from "react-dom";

function UserForm() {
  const { pending } = useFormStatus();

  const handleSubmit = async (event) => {
    event.preventDefault();
    
    // Simulate an asynchronous operation (e.g., API call)
    await new Promise((resolve) => setTimeout(resolve, 2000));
    
    alert("Form submitted successfully!");
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="username">Username:</label>
        <input type="text" id="username" name="username" required />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input type="email" id="email" name="email" required />
      </div>
      <button type="submit" disabled={pending}>
        {pending ? "Submitting..." : "Submit"}
      </button>
    </form>
  );
}

export default UserForm;

Conclusion

Next.js Server Actions simplify server-side operations by allowing developers to define functions that can be called directly from client components without the need for complex API setups.