Fermer

janvier 17, 2024

Démystifier les chargeurs et les actions dans React Router 6 (partie 1)

Démystifier les chargeurs et les actions dans React Router 6 (partie 1)


React Router 6 a introduit un large éventail de nouvelles fonctionnalités qui ont révolutionné la récupération et la soumission de données dans les applications React. Découvrez comment utiliser les chargeurs et les actions pour créer, mettre à jour, supprimer et lire les données des utilisateurs dans cette série en deux parties.

React Router 6 a introduit un certain nombre de nouvelles fonctionnalités dans la version 6.4. Les deux fonctionnalités particulièrement intéressantes sont Chargeurs et Actions qui rationalisent la récupération de données et la soumission de formulaires dans les applications React. Dans cette série, nous expliquerons comment utiliser les chargeurs pour récupérer une liste d’utilisateurs et un utilisateur spécifique pour remplir un formulaire de modification d’utilisateur ainsi que les actions pour créer, modifier et supprimer des utilisateurs. En plus de cela, nous allons plonger dans les nouveaux hooks React Router et les utiliser pour afficher les commentaires de l’interface utilisateur lors de la soumission du formulaire.

Configuration du projet

Pour suivre cet article, exécutez les commandes ci-dessous.

git clone git@github.com:ThomasFindlay/demystifying-loaders-and-actions-in-react-router-6.git
cd demystifying-loaders-and-actions-in-react-router-6
git checkout start
npm install
npm run dev

Ces commandes cloneront le référentiel GitHub pour ce projet, changeront de branche, installeront les bibliothèques et démarreront le serveur de développement. Le projet a quelques bibliothèques déjà installées. Outre le react-router-dom bibliothèque (React Router), nous avons également Zod pour validation, CSS vent arrière pour le style et serveur jsonqui servira de serveur CRUD.

Vous pouvez également trouver un exemple interactif dans le StackBlitz ci-dessous.

Récupérer une liste d’utilisateurs avec un chargeur de routeur React

Commençons par ajouter le routeur au projet. Nous le ferons dans le main.tsx fichier, qui est le point d’entrée de l’application.

main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Users from "./views/user/Users.tsx";
import { usersLoader } from "./views/user/Users.loader.ts";

const router = createBrowserRouter([
  {
    path: "https://www.telerik.com/",
    element: <App />,
    children: [
      {
        index: true,
        element: <Users />,
        loader: usersLoader,
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

Dans le routernous avons précisé que le App Le composant sera rendu lorsque l’URL correspond au / chemin. Actuellement, il a une route enfant, qui sera également rendue lorsque le chemin est /comme il a le index propriété définie sur true. Il y aura plus de routes plus tard pour créer et modifier des utilisateurs.

As-tu vu le loader: usersLoader partie dans la définition de l’itinéraire ? C’est ainsi que nous configurons un chargeur pour un itinéraire spécifique. Avant que React Router ne résolve et affiche le <Users /> élément, il exécutera d’abord le usersLoader chargeur et attendez qu’il se termine. Un gros avantage des chargeurs est qu’ils dissocient la récupération des données du rendu des composants, évitant ainsi le problème de cascade.

Le problème de la cascade fait référence au problème qui survient lorsque les données sont récupérées de manière synchrone et séquentielle, provoquant des retards et bloquant l’exécution des tâches ultérieures. Cela peut devenir perceptible lors de la récupération de données dans une arborescence de composants. Par exemple, si vous disposez d’un composant parent qui doit récupérer des données avant de restituer ses composants enfants. Dans un tel scénario, le rendu de l’ensemble de l’arborescence des composants est bloqué jusqu’à ce que les données soient récupérées, ce qui entraîne un rendu plus lent et une interface utilisateur moins réactive. Et c’est là que les chargeurs brillent, car la récupération des données se fait au niveau de la route plutôt qu’au niveau des composants.

Maintenant, mettons à jour le App composant pour restituer un Outletqui est chargé d’afficher un composant de route qui correspond à l’URL actuelle et au Suspense composant pour afficher un chargeur lorsque les données sont récupérées.

App.tsx

import { Suspense } from "react";
import "./App.css";

import { Outlet } from "react-router-dom";

function App() {
  return (
    <div>
      <Suspense fallback={<div>loading...</div>}>
        <Outlet />
      </Suspense>
    </div>
  );
}

export default App;

Dans le usersLoader nous allons récupérer une liste d’utilisateurs. Mais avant d’y arriver, créons un schéma Zod pour un objet utilisateur.

src/schema/user.schema.ts

import { z } from "zod";

export const userSchema = z.object({
  id: z.union([z.string(), z.number()]),
  firstName: z.string(),
  lastName: z.string(),
});

export type User = z.infer<typeof userSchema>;

Comme vous pouvez le voir, un objet utilisateur sera composé de trois propriétés :id, firstName et lastName. Nous utiliserons Zod pour valider les données récupérées et affiner le type de réponse.

Ensuite, créons le usersLoader méthode.

src/views/user/Users.loader.ts

import { LoaderFunctionArgs } from "react-router-dom";
import { z } from "zod";
import { userSchema } from "../../schema/user.schema";

export const usersLoader = async ({ params }: LoaderFunctionArgs) => {
  const response = await fetch("http://localhost:4000/users");

  const users = await response.json();

  return {
    users: z.array(userSchema).parse(users),
  };
};

export type UsersLoaderResponse = Awaited<ReturnType<typeof usersLoader>>;

La première chose qui arrive dans le usersLoader est une requête API pour récupérer les données des utilisateurs. De plus, le userSchema est utilisé pour valider les données de réponse. Enfin, un objet est renvoyé par le chargeur.

Notez que le point de terminaison de l’API que nous ciblons ici est celui fourni par json-server. Le json-server s’exécute sur le port 4000. Vous pouvez rechercher et modifier les données servies par json-server dans le server/db.json déposer. Outre le usersLoader fonction, nous avons aussi la UsersLoaderResponse taper. Son type est hérité du type de retour du chargeur.

Il est temps de créer le Users composant.

src/views/user/Users.tsx

import { useLoaderData, Link } from "react-router-dom";
import { UsersLoaderResponse } from "./Users.loader";

const Users = () => {
  const { users } = useLoaderData() as UsersLoaderResponse;
  return (
    <div className="max-w-sm mx-auto">
      <h1 className="text-semibold text-2xl mb-6">Users</h1>

      <ul className="space-y-2">
        {users.map(user => {
          return (
            <li key={user.id}>
              <Link to={`/user/${user.id}`} className="hover:underline">
                {user.firstName} {user.lastName}
              </Link>
            </li>
          );
        })}
      </ul>

      <Link
        to="/user/create"
        className="inline-block bg-sky-600 text-sky-50 px-4 py-3 font-semibold w-full mt-4"
      >
        Add User
      </Link>
    </div>
  );
};

export default Users;

React Router fournit le useLoaderData hook pour accéder aux données renvoyées par le chargeur affecté à l’itinéraire. Le type de retour du useLoaderData est unknown par défaut, nous affirmons donc le type de valeur renvoyé à UsersLoaderResponse. Il est dérivé du usersLoader fonctionner dans le Users.loader.tsx déposer.

export type UsersLoaderResponse = Awaited<ReturnType<typeof usersLoader>>;

Par ailleurs, le Users Le composant utilise le tableau de users récupéré par le chargeur pour afficher une liste d’utilisateurs avec leurs noms et prénoms. Chaque élément utilisateur est entouré d’un lien qui redirige vers le formulaire de modification utilisateur. De plus, il existe également un lien vers le formulaire d’ajout d’utilisateur. L’image ci-dessous montre à quoi ressemble le résultat final.

Afficher les utilisateurs

Ensuite, créons des fonctionnalités pour ajouter de nouveaux utilisateurs.

Création d’un nouvel utilisateur avec une action React Router

Jusqu’à présent, nous avons utilisé un chargeur pour récupérer les données des utilisateurs. Maintenant, nous allons utiliser une action pour soumettre les données d’un utilisateur. Nous commencerons par ajouter un nouvel itinéraire dans le main.tsx déposer.

src/main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Users from "./views/user/Users.tsx";
import { usersLoader } from "./views/user/Users.loader.tsx";
import CreateUser from "./views/user/CreateUser.tsx";
import { createUserAction } from "./views/user/CreateUser.action.tsx";

const router = createBrowserRouter([
  {
    path: "https://www.telerik.com/",
    element: <App />,
    children: [
      {
        index: true,
        element: <Users />,
        loader: usersLoader,
      },
      {
        path: "/user/create",
        element: <CreateUser />,
        action: createUserAction,
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

Le CreateUser Le composant sera rendu lorsque l’URL correspond au /user/create chemin. Au lieu d’un loader propriété, nous spécifions un action avec le createUserAction fonctionner comme une valeur. Créons-le maintenant.

src/view/user/CreateUser.action.ts

import { ActionFunctionArgs, redirect } from "react-router-dom";

export const createUserAction = async ({ request }: ActionFunctionArgs) => {
  // Get the form data from the request
  const formData = await request.formData();
  // Convert the form data to an object format
  const payload = Object.fromEntries(formData.entries());

  await fetch("http://localhost:4000/users", {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      ...payload,
      id: Date.now(),
    }),
  });
  
  return redirect("https://www.telerik.com/");
};

React Router s’attend à ce que les données soient transmises à une action en utilisant Données de formulaire. Cependant, comme le serveur attend une charge utile JSON, nous la convertissons de FormData en JSON. Une fois la demande de création d’utilisateur terminée, un utilisateur sera redirigé vers le / chemin.

Ensuite, créons le CreateUser composant.

src/views/user/CreateUser.tsx

import UserForm from "./components/UserForm";

const CreateUser = () => {
  return (
    <div className="max-w-sm mx-auto">
      <UserForm action="/user/create" />
    </div>
  );
};

export default CreateUser;

Le CreateUser Le composant est uniquement responsable de la mise en page et du rendu du UserForm composant. Le UserForm Le composant reçoit le action prop, qui doit correspondre au chemin de route défini pour la route actuelle. React Router l’utilisera pour trouver l’action de route correspondante. Créons le UserForm composant suivant. Nous plaçons le formulaire dans un composant distinct, car nous le réutiliserons à la fois pour la création et la modification des utilisateurs.

src/views/user/components/UserForm.tsx

import { Form } from "react-router-dom";

type UserFormProps = {
  className?: string;
  user?: {
    id: string | number;
    firstName: string;
    lastName: string;
  } | null;
  action: string;
};

const UserForm = (props: UserFormProps) => {
  const { className, user, action } = props;
  return (
    <div className={className}>
      <Form className="space-y-4" method="post" action={action}>
        <input type="hidden" name="id" defaultValue={user?.id} />
        <div className="flex flex-col items-start gap-y-2">
          <label>First Name</label>
          <input
            type="text"
            className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
            name="firstName"
            defaultValue={user?.firstName || ""}
          />
        </div>
        <div className="flex flex-col items-start gap-y-2">
          <label>Last Name</label>
          <input
            type="text"
            className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm"
            name="lastName"
            defaultValue={user?.lastName || ""}
          />
        </div>

        <div>
          <button
            type="submit"
            className="w-full px-4 py-3 mt-4 font-semibold bg-sky-600 text-sky-50"
          >
            Save
          </button>
        </div>
      </Form>
    </div>
  );
};

export default UserForm;

Le UserForm Le composant accepte trois accessoires : className, user et action. Le user prop sera utilisé pour préremplir le formulaire avec les données de l’utilisateur lors de l’édition. Cependant, c’est facultatif, car il n’y a pas encore de données utilisateur lorsque nous en créons une. Le action prop est transmis au Form composant et spécifie à quelle action le formulaire est destiné. Lors de l’utilisation de React Router, l’action doit correspondre au chemin de route du gestionnaire d’action que le formulaire doit déclencher.

Le UserForm Le composant restitue trois entrées, dont deux seulement sont visibles par l’utilisateur. L’un est caché et est utilisé pour stocker l’identifiant de l’utilisateur. Si vous vous demandez pourquoi il n’y a pas useState n’importe où pour stocker l’état du formulaire, c’est parce que, dans cet exemple, nous utilisons des composants non contrôlés. En un mot, l’état du formulaire est contrôlé par le DOM plutôt que par React. Nous fournissons simplement une valeur par défaut s’il y en a pour les entrées.

Voici à quoi ressemble la fonctionnalité.

Créer un utilisateur via l'action React Router

Après avoir soumis le formulaire, nous sommes redirigés vers la page des utilisateurs, où nous pouvons voir qu’un nouvel utilisateur a été ajouté avec succès.

Conclusion

Dans cette partie, nous avons appris à utiliser les chargeurs de routeur React pour récupérer une liste d’utilisateurs et des actions pour créer un nouvel utilisateur. Dans la partie suivante, nous implémenterons des fonctionnalités pour modifier et supprimer des utilisateurs, comment gérer plusieurs actions dans le même itinéraire et ajouter des commentaires sur l’état en attente pour une action de soumission appropriée lorsque le formulaire est en cours de traitement.




Source link