Les actions du serveur Next.js rendues très simples

Puisque les actions serveur sont prêtes dans Next.js, voyons une illustration simple émulant une base de données, gérant les erreurs et utilisant des mises à jour optimistes.
Actions du serveur sont enfin prêts ! J’allais écrire cet article il y a quelques mois, mais il y avait tellement de bugs et de problèmes que j’ai décidé d’attendre. Franchement, je n’utilise React pour aucun projet, et je ne l’aime pas pour de nombreuses raisons (principalement la gestion de l’état), mais j’apprécie Next.js et ce que fait l’équipe. Je suis également un peu optimiste quant aux composants du serveur et aux actions du serveur.
Cet exemple a en fait pris beaucoup plus de temps que prévu ; le Documentation fait extrêmement défaut, surtout lorsqu’il s’agit de TypeScript. Next.js a simplement copié la plupart des exemples de React, et même pas les bons. De plus, la plupart des exemples ne suivent pas le Principe de responsabilité unique, dont je suis un grand fan. Alors, commençons par un exemple très simple !
Ajouter un nom d’utilisateur
Avec les composants React Server, la bonne façon de vous connecter au serveur pour modifier vos données est d’utiliser Actions du serveur. Svelte a des actions depuis un certain temps maintenant, mais React fonctionne un peu différemment. Certaines choses sont extrêmement intuitives, d’autres non. Pour vous éviter d’avoir besoin d’être un expert React, j’ai simplifié les choses. L’exemple le plus simple auquel je puisse penser est l’ajout d’un nom d’utilisateur à une base de données. Cet exemple émule une base de données, gère les erreurs et utilise des mises à jour optimistes.
utiliserFormState
Vous ne pouvez pas utiliser useFormState
avec useOptimistic
en plus je trouve la frappe et la mécanique du useFormState
un peu bizarre. Nous devons parfois renvoyer des données du serveur, pas seulement une erreur. Quoi qu’il en soit, vous n’en avez pas besoin ici.
page.tsx
import { Username } from "./username";
export default function Home() {
return (
<main>
<Username />
</main>
);
}
Rien de surprenant ici sur la page principale.
nom d’utilisateur.tsx
'use client';
import { useRef } from "react";
import { useUsername } from "./use-username";
import { UsernameStatus } from "./username-status";
export function Username() {
const formRef = useRef(null);
const { username, usernameError, addUsernameAction } = useUsername(formRef);
return (
<section className="m-10">
<h1 className="text-2xl border-b-2 border-black my-3">Add a Username</h1>
<form className="flex flex-col gap-3" ref={formRef} action={addUsernameAction}>
<input className="border border-sky-700 p-3" type="text" name="username" placeholder="Username" />
{usernameError &&
<p className="text-red-500 text-xs my-2">
{usernameError}
</p>
}
<button className="bg-sky-700 text-white mt-3" type="submit">Add</button>
<p>Current Username: {username}</p>
<UsernameStatus />
</form>
</section>
);
}
D’accord. Que se passe t-il ici? Premièrement, nous créons un useRef
crochet pour le formulaire. Cela nous permettra de réinitialiser le formulaire plus tard lorsque nous le transmettrons à notre hook personnalisé. Remarquez à quel point cela est beau. Je vous suggère fortement de toujours créer un hook personnalisé lorsque vous traitez un état complexe. Cela vous permet de rester en phase avec le Principe de responsabilité uniqueet rend finalement votre code plus facile à lire, à mettre à jour et à gérer pour un seul créateur ou une équipe.
Ensuite, nous créons un formulaire avec formRef
et action
donnez-lui une belle apparence avec Tailwind, affichez le error
(s’il y en a un), et montrez le username
État. Tout cela est du React standard, mais nous ne nous enlisons pas à essayer de le comprendre ; notre code est dans le hook personnalisé.
nom d’utilisateur-statut.tsx
import { useFormStatus } from "react-dom";
export function UsernameStatus() {
const status = useFormStatus();
return (
<pre>
<p>Status: {JSON.stringify(status, null, 2)}</p>
</pre>
);
}
Ce code est également extrêmement facile à comprendre et vous montre simplement l’état du formulaire ainsi que les données. Vous ne verrez pas les données soumises, car il s’agit techniquement d’un formData
taper. Vous devrez obtenir les données du formulaire comme n’importe quel autre formulaire, puis les utiliser en conséquence.
Je n’ai pu voir une utilité à cette méthode que dans de rares circonstances, car vous pouviez simplement intercepter l’action du formulaire et obtenir les données directement dans la plupart des cas. Cependant, le statut pending
est très utile pour désactiver les boutons, afficher les états de chargement, etc.
ajouter-nom d’utilisateur.tsx
type AddUsername = {
success: false;
error: string;
} | {
success: true;
username: string;
};
export async function addUsername(formData: FormData): Promise<AddUsername> {
const { username } = Object.fromEntries(formData);
if (!username || typeof username !== 'string' || username.length < 2) {
return {
success: false,
error: 'Username must be at a valid string of at least 2 characters'
};
}
// simulate adding to database
await new Promise((res) => setTimeout(res, 3000));
return {
success: true,
username: 'server-' + username
};
}
Ok, parlons de l’action du formulaire elle-même. Ici, nous validons le nom d’utilisateur et renvoyons une erreur ou le nouveau nom d’utilisateur. Notez que mes types de retour sont false
et true
autre que boolean
. Il s’agit d’une astuce pour vous aider avec les types de retour valides. S’il revient success: true
, alors il doit y avoir un nom d’utilisateur. Sinon success: false
renverra une erreur.
Je recommande vivement Valeur sur le serveur au lieu de Zod. Zod est trop compliqué, et surtout, n’utilise pas Arbre secoué. Cela signifie que vous importerez des classes et des méthodes dont vous n’avez pas besoin, ce qui ralentira votre serveur. Quoi que vous fassiez, n’utilisez PAS Zod sur le frontend. Je ne sais pas d’où cela vient pour les autres créateurs de contenu, mais HTML5 a validateurs intégrés pour ça.
utiliser-nom d’utilisateur.tsx
import { useState, type MutableRefObject, useOptimistic } from "react";
import { addUsername } from "./add-username";
export function useUsername(formRef: MutableRefObject<HTMLFormElement | null>) {
const [usernameError, setUsernameError] = useState('');
const [username, setUsername] = useState('');
const [optimisticUsername, setOptimisticUsername] = useOptimistic(username);
return {
username: optimisticUsername,
usernameError,
addUsernameAction: async (formData: FormData) => {
const { username } = Object.fromEntries(formData);
formRef.current?.reset();
setUsernameError('');
if (typeof username !== 'string') {
return;
}
setOptimisticUsername('client-' + username);
const result = await addUsername(formData);
if (!result.success) {
setUsernameError(result.error);
return;
}
setUsername(result.username);
}
};
};
Ok, passons maintenant à la beauté du code : le hook personnalisé. Ici le useUsername
le crochet prend le formRef
comme paramètre pour réinitialiser le formulaire si nécessaire. Créer un usernameError
état pour gérer les erreurs, username
état à gérer avec le nom d’utilisateur réel et optimisticUsername
pour gérer la vision optimiste de l’utilisateur du nom d’utilisateur.
La plupart des exemples que j’ai trouvés trop compliqués useOptimistic
. Pensez-y comme useState
, sauf qu’il est automatiquement annulé lorsque les données du serveur sont renvoyées. Vous devez setUsername
après tout, c’est dit et fait pour qu’il y ait une valeur réelle lorsque le serveur est terminé.
Et vous pouvez voir les données d’entrée changer de client
à server
après quelques secondes. C’est ce que font les mises à jour optimistes.
Gestion des erreurs dans les actions du serveur
Il existe techniquement deux manières de gérer les erreurs dans les actions du serveur :
- Utilisez un Limite d’erreur. Dans ce cas, vous lancez une erreur dans votre
addUsername
action. Cependant, je ne suis pas fan de l’utiliser dans ce cas. J’ai l’impression que cela devrait quelque peu modéliser ce que fait une API Rest. Cependant, ce n’est pas faux non plus. - Renvoyez simplement l’erreur dans un objet. S’il y a une erreur, renvoyez simplement l’erreur et affichez-la en conséquence. La seule différence est que l’API Rest renverrait un
400
ou500
erreur, que vous devrez vérifier avecresponse.ok
. Les problèmes de connexion doivent également être traités dans votre code. La plupart des autres exemples ne gèrent même pas les erreurs.
Ici, s’il y a une erreur, il vous suffit de l’imprimer.
Je viens de faire cette erreur à titre d’exemple, mais dans une vraie application, vous géreriez la validation du formulaire à la fois sur le backend et sur le frontend.
C’est ça! Assez simple, hein ? La clé ici est que vous utilisiez un hook personnalisé. React n’est sans doute pas conçu pour les programmeurs utilisant le Principe de responsabilité uniquevous devez donc programmer de manière responsable et optimiste.
Dépôt : GitHub
Source link