Nel precedente articolo, abbiamo visto cosa sono e a cosa servono i nuovi Server Components di React.js e Next.js. Adesso vedremo un'altra funzionalità del nuovo Next.js 14: le Server Actions.
Le Server Actions sono delle funzioni asincrone eseguite sul server, generalmente usate per gestire la fase di submission dei form e la mutazione di dati.
In Vanilla React, la gestione dei form avviene tendenzialmente leggendo i singoli campi degli input e inviandoli attraverso una fetch intercettando il click del pulsante submit. Buona prassi è anche quella di usare librerie esterne per la validazione, come React Hook Form.
Questo approccio cambia radicalmente con le Server Actions di Next.js 14, potendo inserire una funzione all’interno dell’attributo action del form:
1export default function NewPostPage() {
2 const createPost = async (formData: FormData) => {
3 "use server";
4 console.log(formData)
5 }
6
7 return (
8 <form action={createPost}>
9 <label>Title</label>
10 <input type="text" name="title" />
11 <button type="submit">Submit</button>
12 </form>
13 );
14}
In questo component dobbiamo attenzionare alcune nozioni importanti:
Astrazione della fetch in POST al Submit
⚠️ L’attributo action del form non è quello di default di HTML, ma è una versione estesa da Next.js la quale la crea in maniera astratta l’endpoint POST
I vantaggi di tale approccio sono molteplici:
L’unico svantaggio invece è il seguente:
Il backend è un tutt’uno col frontend, ma in questo modo gli altri client non possono usare l’action (in tale caso meglio usare le Route Handlers, ex API Routes).
Per convezione e buona prassi, possiamo spostare tutte le Server Actions (le funzioni indicate con “use server") in un file separato denominato action.ts all’interno della rotta, con la direttiva in cima al file.
1import { createPost } from "./action";
2
3export default function NewPostPage() {
4 return (
5 <form action={createPost}>
6 <label>Title</label>
7 <input type="text" name="title" />
8 <button type="submit">Submit</button>
9 </form>
10 );
11}
1"use server";
2
3export async function createPost(formData: FormData) {
4 // ...
5}
In questo modo, tutte le funzioni dentro action.ts saranno server actions grazie alla corrispondente direttiva in cima al file.
L’oggetto FormData che ci arriva come parametro dall’action del form, è composto da un insieme di chiave/valore corrispondenti alle entry del form.
Possiamo quindi usare dei metodi specifici per prendere le informazioni del form e usarle per manipolarle, validarle o salvare nel DB.
1"use server";
2
3import { sql } from "@vercel/postgres";
4
5export async function createPost(prevState: any, formData: FormData) {
6 const title = formData.get("title") as string;
7 const content = formData.get("content") as string;
8 const publishedAt = new Date(Date.now()).toISOString();
9
10 await sql`
11 INSERT INTO posts (title, content, publishedAt)
12 VALUES (${title}, ${content}, ${publishedAt})
13 `;
14}
Le Server Actions sono dunque il posto perfetto dove implementare tutte le operazioni CUD (create, update, e delete) per il DB (il read lo abbiamo già visto con le fetch di un un normale server component).
⚠️ Anche se facciamo operazioni di update e delete, le Server Actions sono sempre inizializzate con metodo POST.
Insieme a questo nuovo approccio per la gestione dei form attraverso le Server Actions, Next.js 14 mette a disposizione due nuovi hook per gestire lo stato dei form.
In base allo stato della rete, le actions potrebbero prendere del tempo per ritornare una risposta della corretta esecuzione. Possiamo quindi mostrare lo stato della submission usando l’hook useFormStatus.
Caratteristiche dell’hook:
1import SubmitPostButton from "@/app/components/SubmitPostButton";
2import { createPost } from "./action";
3
4export default function NewPostPage() {
5 return (
6 <form action={createPost}>
7 <label>Title</label>
8 <input type="text" name="title" />
9 <SubmitPostButton />
10 </form>
11 );
12}
1"use client";
2
3import { useFormStatus } from "react-dom";
4
5export default function SubmitPostButton() {
6 const { pending } = useFormStatus();
7
8 return (
9 <button type="submit" disabled={pending}>
10 Add
11 </button>
12 );
13}
Oltre allo stato del form, possiamo leggere una eventuale risposta custom dalla Server Actions, o un errore, ritornando un oggetto dalla action e leggendolo con l’hook useFormState.
Per fare ciò dobbiamo fare alcune modifiche incisive sia al form che all’action.
Per quanto riguarda l’action:
Ritornare il messaggio o l’errore come oggetto.
1"use server";
2
3export async function createPost(prevState: any, formData: FormData) {
4 //...
5 try {
6 throw new Error("Something went wrong");
7 } catch (error) {
8 return { error: (error as Error).message };
9 }
10 return { message: "Post created successfully" };
11}
Modifiche al form:
Così abbiamo a disposizione nel component form l’oggetto message o error precedentemente tornato dalla createPost.
1"use client";
2
3import { useFormState } from "react-dom";
4
5export default function NewPostPage() {
6 const [state, formAction] = useFormState(createPost, {
7 message: "",
8 });
9
10 if (state?.error) {
11 return <p>{state?.error}</p>;
12 }
13
14 return (
15 <form action={formAction} className={styles.container}>
16 {/* ... */}
17 <p>{state?.message}</p>
18 </form>
19 );
20}
Come abbiamo visto, Next.js 14 sta inglobando al suo interno molte caratteristiche che prima erano esclusivamente di dominio del backend. L’astrazione che implementa per passare da ambiente client a ambiente server è una peculiarità unica nel mondo web development, mai vista in un framework nato per il frontend.
Questo nuovo approccio fa riflettere sulle barriere che ci piace darci per definire il nostro dominio di competenza, anche se dovremmo essere concentrati più a realizzare delle web app efficaci, e non a darci dei limiti. Ma se proprio non potete fare a meno dei titoli, adesso che state a tutti gli effetti sviluppando anche sul server, se prima eravate frontend developer vi autorizziamo a scrivere fullstack developer sull’headline di LinkedIn :)
Programmatore a tutto tondo, ha avuto esperienza dal mondo dello sviluppo di applicazioni 3D (videogames, virtual reality e augmented reality) al mondo dello sviluppo per il web.
Appassionato di tecnologia e informatica, dopo gli studi ha fatto molteplici esperienze internazionali per poi tornare nella sua amata Sicilia. Lavora principalmente con JavaScript/TypeScript, React.js/Next.js e Node.js lato web, Unity/C#, Unreal Engine e Three.js lato 3D.
Ha a cuore la condivisione delle skills e delle conoscenze, e gli piace dare una mano concreta nelle community.