Requerimientos

  • Bases de Nextjs

Que son los Server Actions

  • Permite crear mutaciones desde el lado del servidor sin la necesidad de crear Endpoints de API Inncesarios
  • Reduce la cantidad de codigo de Javascript del lado cliente
  • Soporta la mejora de formularios de forma progresiva
  • Server Actions puede funcionar dentro de componentes del lado Servidor (Server Components) y tambien desde componnetes del lado Cliente.

Creación de un proyecto de Nextjs

npx create-next-app nextjs-server-actions

o mas resumido:

npx create-next-app --ts --tailwind --app --src-dir --eslint next-server-actions

Luego de haber creado el proyecto, actualiza el src/index.css de la siguiente forma:

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
    @apply bg-gray-950
}

Creación de Formulario Básico

export default function App() {
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <form>
        <input
          type="text"
          placeholder="name"
          className="border border-gray-300 rounded-md p-2 block mb-2"
        />
        <input
          type="text"
          placeholder="price"
          className="border border-gray-300 rounded-md p-2 block"
        />
        <button className="bg-blue-500 text-white rounded-md p-2 mt-2 w-full">
          Create Product
        </button>
      </form>
    </div>
  );
}

Usando este formulario creeriamos que necesitamos una api o usar un "use client" para poder manejar el estado y eventos del formulario, pero podemos usar solo codigo de servidor para manejar esto.

Server Action

Actualizaremos el formulario de esta forma:

import { randomUUID } from "crypto";

export default function App() {
  const saveProduct = async (formData: FormData) => {
    'use server'
    console.log(formData);

    const name = formData.get("name")?.toString();
    const price = formData.get("price")?.toString();

    if (!name || !price) {
      return;
    }

    const newProduct = {
      name,
      price: parseInt(price),
      id: randomUUID(),
    };

    console.log(newProduct);

    // save to database or send to an API
  };

  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <form action={saveProduct}>
        <input
          type="text"
          name="name"
          placeholder="name"
          className="border border-gray-300 rounded-md p-2 block mb-2"
        />
        <input
          type="text"
          name="price"
          placeholder="price"
          className="border border-gray-300 rounded-md p-2 block"
        />
        <button className="bg-blue-500 text-white rounded-md p-2 mt-2 w-full">
          Create Product
        </button>
      </form>
    </div>
  );
}

en donde estamos usando lo siguiente

  • todos los inputs tienen una propiedad name
  • se ejecuta el action
  • la funcion que maneja el action recibe toda la informacion del formulario de tipo FormData
  • dentro de la funcion saveProduct estamos usando un 'use server' para indicar que se procesara en el servidor
  • los server actions deben ser funciones async

Cuando se envia un formulario con un server action,

Server Actions pueden ser componibles

lo que quiere decir que se pueden mover a un archivo por aparte

crea un archivo en src/actions/serverActions.ts

"use server";
import { randomUUID } from "crypto";

export const saveProduct = async (formData: FormData) => {
  console.log(formData);

  const name = formData.get("name")?.toString();
  const price = formData.get("price")?.toString();

  if (!name || !price) {
    return;
  }

  const newProduct = {
    name,
    price: parseInt(price),
    id: randomUUID(),
  };

  console.log(newProduct);

  // save to database or send to an API
};
  • Aqui se ha movido el use server fuera de la funcion, lo que indica que cualquier funcion debajo de esta declaracion es un server action

Server Actions del lado cliente

Primero vamos a crear una copia del formulario para poder empezar a compararlo con la opcion del lado del servidor:

"use client";

import { saveProduct } from "@/actions/serveActions";

export default function App() {
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <form action={(e) => saveProduct(e)}>
        <input
          type="text"
          name="name"
          placeholder="name"
          className="border border-gray-300 rounded-md p-2 block mb-2"
        />
        <input
          type="text"
          name="price"
          placeholder="price"
          className="border border-gray-300 rounded-md p-2 block"
        />
        <button className="bg-blue-500 text-white rounded-md p-2 mt-2 w-full">
          Create Product
        </button>
      </form>
    </div>
  );
}

Cuando un server action se ejecuta del lado cliente este hace una peticion POST, por lo que si quieren verlo pueden colocar esta opcion en el archivo next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
    logging: {
        fetches: true,
    }
};

export default nextConfig;

Ademas de revisar la pestaña Network del navegador.

Asi que lo que podemos hacer es usar el hook de react useTransition para poder manejar esto y colocar un estado de Pendiente mientras la respuesta de la peticion POST va llegando.

"use client";

import { saveProduct } from "@/actions/serveActions";
import { useTransition } from "react";

export default function App() {
  const [isPending, startTransition] = useTransition();

  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <form action={(e) => startTransition(() => saveProduct(e))}>
        <input
          type="text"
          name="name"
          placeholder="name"
          className="border border-gray-300 rounded-md p-2 block mb-2"
        />
        <input
          type="text"
          name="price"
          placeholder="price"
          className="border border-gray-300 rounded-md p-2 block"
        />
        <button className="bg-blue-500 text-white rounded-md p-2 mt-2 w-full">
          {isPending ? "Creating Product..." : "Create Product"}
        </button>
      </form>
    </div>
  );
}

o usar cualquier hook que queramos en realidad

Aqui usamos un useTransition porque este hace una peticion por POST y esta puede tomar tiempo asi que es bueno colocar

Más Recursos

https://youtu.be/W6V91VeghjI?t=175