Next.js 14 Server Actions : Revolutionizing Web Development:

  • Posted on February 6, 2024
  • By MmantraTech
  • 422 Views

According to the official website:

Server Actions are asynchronous functions that are executed on the server. They can be used in Server and Client Components to handle form submissions and data mutations in Next.js applications.

You can take  server functions as PHP ( is a server side language ) page where data mutation happens in Server Side  you get response.

NOTE : There is difference between Server Action and Server Components

 

Grey And Blue Modern Technology Business Presentation-N8E8ki9EeZ.png

This tutorials will explain you the following points:

  • How to use server action in ServerComponents.
  • How to use Server Action in Client Components.
  • How to use Server action as a prop in client components from Server.
  • How to separate Server Action in separate file 
  • Refactoring Components with Server Action

 

So, In the above picture, it is very clear that Server actions saves our time and to and fro between files. Using server actions, you can directly interact with the database ORM and you are no longer dependent on API Routes.

Unveiling the Magic of Server Actions:

Next.js Server Actions represent a paradigm shift in server-side execution. These specialized functions exclusively operate on the server, allowing developers to delegate tasks like data fetching and mutations. This approach mitigates vulnerabilities associated with client-side data handling, enhancing security.

To grasp the significance of Server Actions, it's crucial to explore the problems they address, the prior methods, and their evolution. React Server Components (RSCs) emerged to address the complexity of SSR, CSR, and SSG, providing a flexible solution for separating concerns.

Server Actions in Next.js are built upon the concept of React Actions. This empowers developers to dictate where components execute, distinguishing between server-side and client-side responsibilities.

What makes Server Actions remarkable?

  1. Instantaneous forms: Bid farewell to page reloads! Server Actions swiftly handle form submissions, delivering a seamless user experience.
  2. Data manipulation: Seamlessly update information, create entries, or delete content without page refreshes—a coding magic trick enhancing user satisfaction.
  3. Security superhero: Shield sensitive data and logic from prying eyes, fortifying your website against potential threats and ensuring user privacy.

 

How do Server Actions operate? Let's delve into an example!

Let's begin with a simple example to grasp Server Actions. We'll insert movie names into a MySQL database and retrieve data accordingly. This time, we won't use API routes for interaction. But how do we interact with the database? You're aware that for database mutation, we typically require either APIs or built-in ORM within the framework. That's where Server Actions come in. They are asynchronous functions executed on the server, handling interactions with the database and other tasks.

Project Setup

Step 1: Add following code in app/page.js

import Image from "next/image";
import Movies from "./movies/page";

export default function Home() {
  return (
    <div className="flex flex-col justify-center items-center">
      <div className="mx-auto max-w-2xl">
      <Movies/>
    </div>
    </div>

  );
}

Step 2: Add below code in app/movies/page.jsx

import executeQuery from "@/util/Db";
import { revalidatePath } from "next/cache";

const getData = async () => {
  "use server"
  try {
    const result = await executeQuery({
      query: "select * from tbl_movies",
      values: [0],
    });
    return result;
  } catch (err)
  {

  }
};

const handleSubmit = async (form) => {
  "use server";
  const data = {
    movie_name: form.get("movie"),
  };

  try {
    const result = await executeQuery({
      query: "insert into tbl_movies set name=?",
      values: [data.movie_name],
    });

    // revalidatePath("/")

    return { msg: "inserted successfully" };
  } catch (error) {
    return { msg: error };
  }
};


async function Movies() {

  const movies = await executeQuery({
    query: "select * from tbl_movies",
    values: [0],
  });


  return (
    <div className="flex flex-col justify-between items-center">
      <div className="border-2 border-gray-500 shadow-lg rounded mt-20 py-14 px-24 w-[500px]">
        <form action={handleSubmit}>
          <div className="flex flex-col justify-center items-center">
          <label className="font-bold">Movie Name : </label>
            <input
              type="text"
              name="movie"
              className="border-2 border-gray-300 py-2 rounded-md"
            />

            <button
              type="submit"
              className="border-2 border-gray-300 mt-2 bg-blue-500 text-white py-2 px-3 rounded-md"
            >
              Submit
            </button>
          </div>
        </form>
      </div>
      <div className="border-2 border-gray-500 shadow-lg rounded mt-2 py-14 px-24 w-[500px]">

        <ul>
          {movies.map((item,index) => {
            return <li className="list-disc" key={index}>{item.name}</li>
          })}
        </ul>
      </div>
    </div>
  );
}

export default Movies;

In above code you can everything is in one place. Using "use server" directive we are directly interacting with database ORM. We dont need to use API Routes for this.

By default, all pages in the App Directory are Server Components. So, if you want to use Server Actions in Server Components, you need to include the "use server" directive within the function's body, as shown in the example code above. When you include "use server" in a function, it tells Next.js to process it on the server side. In the example, when a form is submitted, the form action calls a server-side function, and the form data is sent to the server, while residing in Server Components.

However, there's a problem when you need client interactivity, like using useState, onClick, or useEffect in the Components. These features can only be used in Client Components.

Step 4: Paste below code in util/Db.js

import mysql from "mysql2/promise";

export default async function query({ query, values = [] }) {

  const dbconnection = await mysql.createConnection({
  // const dbconnection =  mysql.createPool({
    host: process.env.MYSQL_HOST,
    port: process.env.MYSQL_PORT,
    database: process.env.MYSQL_DATABASE,
    user: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
  });



  try {
    const [results] = await dbconnection.execute(query, values);
    console.log("Connected")
    dbconnection.end();
    return results;
  } catch (error) {
    throw Error(error.message);
    return { error };
  }
}

Step 5: Configure file next.configure.mjs

/** @type {import('next').NextConfig} */
const nextConfig = {
  env: {
    MYSQL_HOST: "localhost",
    MYSQL_PORT: "3306",
    MYSQL_DATABASE: "movies",
    MYSQL_USER: "root",
    MYSQL_PASSWORD: "",
  },
};

export default nextConfig;

The solution is to refactor your Components into Client and Server Components. Next.js architecture allows you to separate Client and Server Components. So, code that requires interactivity (useState, onClick) should be configured in Client Components, while Components that need database access and API mutation should be placed in Server Components.

NOTE: You can use the "use server" directive only with Server Components. If you try to use the same scenario with Client Components, you may get the following error from Next.js:

"It is not allowed to define inline "use server" annotated Server Actions in Client Components."

So, how do you use Server Actions in Client Components? As the Next.js site suggests:

"Client Components can only import actions that use the module-level "use server" directive.

To call a Server Action in a Client Component, create a new file and add the "use server" directive at the top of it. All functions within the file will be marked as Server Actions that can be reused in both Client and Server Components."

So, you need to separate Server actions into another file and call them in Client Components. Let's continue our example.

Ok. Lets do above code example by above suggestion options.

Step 6: Replace your code in file  app/movies/page.jsx by following :

import GetMovies from "../components/client/page";
import { addMovie, getMovies } from "@/app/actions/ServerActions";

async function  Movies() {

  const result = await getMovies();

  return (
    <div>
      <GetMovies movies={result.data} />

    </div>
  );
}

export default Movies;

As you can see we have create client component "<GetMovies/> Separately. you just need following steps :

Step 7 : Create a file app/components/client/page.jsx and paste below code :

"use client";
import React, { useEffect } from "react";
import { addMovie, getMovies } from "@/app/actions/ServerActions";
import { revalidatePath } from "next/cache";


function GetMovies({movies}) {
    const handleSubmit = async (form) => {
    const data = {
      movie_name: form.get("movie"),
    };
    const result = await addMovie(data);
    // revalidatePath("/")
  };

  return (
    <div className="flex flex-col justify-between items-center">
      <div className="border-2 border-gray-500 shadow-lg rounded mt-20 py-14 px-24 w-[500px]">
        <form action={handleSubmit}>
          <div className="flex flex-col justify-center items-center">
            <input
              type="text"
              name="movie"
              className="border-2 border-gray-300 py-2 rounded-md"
            />

            <button
              type="submit"
              className="border-2 border-gray-300 mt-2 bg-blue-500 text-white py-2 px-3 rounded-md"
            >
              Submit
            </button>
          </div>
        </form>
      </div>
      <div className="border-2 border-gray-500 shadow-lg rounded mt-2 py-14 px-24 w-[500px]">

        <ul>
          {movies.map((item,index) => {
            return <li key={index}>{item.name}</li>
          })}
        </ul>
      </div>
    </div>
  );
}

export default GetMovies;

Step 8 : Create a file app/actions/ServerActionsj.s and paste below code :

"use server";
import React from "react";
import executeQuery from "@/util/Db";
import { revalidatePath } from "next/cache";
export const getMovies = async () => {
  try {
    const result = await executeQuery({
      query: "select * from tbl_movies",
      values: [0],
    });
    return { msg: "success", data: result };
  } catch (err) {
    return { msg: "failure", data: err };
  }
};
export const addMovie = async (data) => {
  try {
    const result = await executeQuery({
      query: "insert into tbl_movies set name=?",
      values: [data.movie_name],
    });
    revalidatePath("/");
    return { msg: "inserted successfully" };
  } catch (error) {
    return { msg: error };
  }
};

 

Server Actions as a prop to Client Component from Server Coponent

 This is also suggested scenario in Next.js Website . To achieve this you just need to update  app/movies/pages.js file by below code 

import GetMovies from "../components/client/page";
import { addMovie, getMovies } from "@/app/actions/ServerActions";

async function  Movies() {

  const result = await getMovies();

  return (
    <div>
      <GetMovies movies={result.data} />

    </div>
  );
}

export default Movies;

And in components/client/pages.jsx file you have to recevie it like below 

"use client";
import React, { useEffect } from "react";
import { addMovie, getMovies } from "@/app/actions/ServerActions";
import { revalidatePath } from "next/cache";


function GetMovies({movies}) {
    const handleSubmit = async (form) => {
    const data = {
      movie_name: form.get("movie"),
    };
    const result = await addMovie(data);
    // revalidatePath("/")
  };

  return (
    <div className="flex flex-col justify-between items-center">
      <div className="border-2 border-gray-500 shadow-lg rounded mt-20 py-14 px-24 w-[500px]">
        <form action={handleSubmit}>
          <div className="flex flex-col justify-center items-center">
            <input
              type="text"
              name="movie"
              className="border-2 border-gray-300 py-2 rounded-md"
            />

            <button
              type="submit"
              className="border-2 border-gray-300 mt-2 bg-blue-500 text-white py-2 px-3 rounded-md"
            >
              Submit
            </button>
          </div>
        </form>
      </div>
      <div className="border-2 border-gray-500 shadow-lg rounded mt-2 py-14 px-24 w-[500px]">

        <ul>
          {movies.map((item,index) => {
            return <li key={index}>{item.name}</li>
          })}
        </ul>
      </div>
    </div>
  );
}

export default GetMovies;

Concluson and Best Practices:

  • Always make Client and Server Components separately as per requirement.
  • Keep user interactivity in Client Side like useState, useEffect, onClick.
  • Keep Server interactivity in Server Components like Database and API mutations.
  • Keep Server Actions (which are actually async functions, only interacting with databases and other parties) in separate files in the project and call them in client and Server Components.
  • The benefit of keeping Server Actions separately from Components is that we need to use the "use server" directive in one place, and we can use them in both client and Server Components as needed.

 

PRO Tips :

  1. Caching and revalidation: Seamlessly integrates with Next.js's caching system, ensuring data freshness and optimal server performance.
  2. Error handling: Server Actions gracefully manage errors, providing informative messages to users for a user-friendly website.
  3. Flexibility: Extend usage to diverse form elements, buttons, and beyond, showcasing versatility in web development.

 

Author
No Image
Admin
MmantraTech

Mmantra Tech is a online platform that provides knowledge (in the form of blog and articles) into a wide range of subjects .

Write a Response