React - rocambille/start-express-react GitHub Wiki
React est une bibliothèque JavaScript permettant de créer des interfaces utilisateur. Voici un exemple de code pour mesurer si vous êtes à l'aise avec l'outil :
import { createRoot } from "react-dom/client";
function HelloMessage({ who }) {
return <p>hello, {who}!</p>;
}
const root = createRoot(document.getElementById("container"));
root.render(<HelloMessage who="world" />);Si vous visualisez le message "hello, world!" affiché dans un paragraphe, vous maitrisez les bases de la bibliothèque.
Sinon, nous vous recommandons de revoir les 80% de concepts React avant d'aller plus loin.
Couplé à React Router en mode data, StartER fournit un point d'entrée pour vos pages React dans le fichier src/react/routes/tsx : c'est dans ce fichier que vous devez déclarer vos routes React. Chaque route comporte deux parties obligatoires : un modèle d'URL et un élément à afficher quand le modèle matche avec l'URL courante dans la barre d'URL du navigateur.
Si vous souhaitez réaliser une SPA, votre fichier routes.tsx devrait ressembler à :
import App from "./components/App";
export default [
{
path: "/",
element: <App />
},
];Où App est le composant racine de votre SPA.
Dans le cas d'une MPA, votre fichier routes.tsx devrait exporter un tableau qui définit plusieurs routes. Un usage courant est d'ajouter un sytème de "layout" pour les éléments communs entre les pages : l'élément <Outlet /> permet l'injection de la partie qui varie d'une page à une autre dans le <Layout>.
function Layout({ children }) {
return (
<>
<Header />
<main>
{children}
</main>
<Footer />
</>
);
}
export default [
{
path: "/",
element: (
<Layout>
<Outlet /> {/* <Home /> or <About />,
depending on the URL in the browser */}
</Layout>
),
children: [
{
index: true,
element: <Home />,
},
{
path: "/about",
element: <About />,
},
],
},
];Une fois vos routes définies, vous pouvez leur associer des composants React. StartER fournit un exemple complet autour d'une ressource item, illustrant un CRUD complet.
StartER propose des exemples de composants pour manipuler des items (un item est ici un objet fictif avec un titre) :
- le composant
<ItemList>affiche une liste d'items, - le composant
<ItemShow>affiche un item en lecture seule et un formulaire de suppression<ItemDeleteForm>, - le composant
<ItemEdit>affiche un formulaire<ItemForm>pour modifier un item, - le composant
<ItemCreate>affiche un formulaire<ItemForm>pour créer un item.
Ces composants forment un CRUD pour les items, car ils fournissent les interfaces graphiques pour créer, lire, mettre à jour et supprimer des items.
Chaque composant utilise les données et la logique fournies par un hook personnalisé : dans le cas des items, c'est le hook useItems.
Enfin, un fichier index.tsx définit les routes liées aux items.
import type { RouteObject } from "react-router";
import ItemCreate from "./ItemCreate";
import ItemEdit from "./ItemEdit";
import ItemList from "./ItemList";
import ItemShow from "./ItemShow";
export const itemRoutes: RouteObject[] = [
{
path: "/items/new",
element: <ItemCreate />,
},
{
path: "/items/:id/edit",
element: <ItemEdit />,
},
{
path: "/items",
element: <ItemList />,
},
{
path: "/items/:id",
element: <ItemShow />,
},
];Les composants de ce CRUD se trouvent dans les sous-dossiers item du dossier src/react/components.
src/react
├── routes.tsx
└── components
└── item
├── hooks.ts
├── index.tsx
├── ItemCreate.tsx
├── ItemDeleteForm.tsx
├── ItemEdit.tsx
├── ItemForm.tsx
├── ItemList.tsx
└── ItemShow.tsx
C'est une manière de faire proposée en exemple : vous êtes libres de l'adapter ou d'organiser vos composants d'une manière complètement différente.
💡 Pour créer un nouveau module React à partir du module item, utilisez le script de clonage :
npm run make:clone src/react/components/item src/react/components/post Item PostCe script duplique tous les fichiers du module et remplace automatiquement les identifiants Item par Post. Vous obtenez ainsi un CRUD React complet prêt à personnaliser.
Un hook personnalisé est une fonction réutilisable qui encapsule de la logique liée à un besoin spécifique, comme la récupération ou la mise à jour de données.
Le fichier hooks.ts déclare un hook personnalisé : useItems.
export const useItems = () => {
// do something
};Ce hook personnalisé nous permet d'encapsuler une logique et d'en retourner une représentation.
export const useItems = () => {
// do some logic
return {
// some values resulting from logic
};
};Reste à remplir la valeur retournée par useItems. C'est là que le hook personnalisé commence à jouer son rôle : récupérer les données.
import { cache } from "./utils";
export const useItems = () => {
const items = use<Item[]>(cache("/api/items"));
return {
items,
};
};La fonction utilitaire cache permet de fetcher les données avec une mise en cache : c'est nécessaire pour éviter une boucle infinie d'appels avec la fonction use de React.
Également, useItems fournit des fonctions de rappel pour mettre à jour les items : ces fonctions ont la charge d'invalider explicitement le cache.
import { cache, invalidateCache } from "../utils";
export const useItems = () => {
const items = use<Item[]>(cache("/api/items"));
const addItem = (partialItem: Omit<Item, "id">) => {
// ...
invalidateCache("/api/items");
};
return {
items,
addItem,
};
};Voir le code complet pour les détails :
De cette façon, l'essentiel de la logique est centralisé dans useItems. Le reste des composants déroule le minimum d'affichage pour réaliser les fonctionnalités du CRUD et permettre la navigation entre les pages.
Objectifs :
- Afficher la liste des items.
- Fournir un lien d'accès vers la page Show de chaque item.
- Fournir un lien d'accès vers la page Create.
import { Link } from "react-router";
import { useItems } from "./hooks";
function ItemList() {
const { items } = useItems();
return (
<>
<h1>Items</h1>
<Link to="/items/new">Ajouter</Link>
<ul>
{items.map((item) => (
<li key={item.id}>
<Link to={`/items/${item.id}`}>{item.title}</Link>
</li>
))}
</ul>
</>
);
}
export default ItemList;Objectifs :
- Afficher les détails de l'item courant (le titre).
- Fournir un lien d'accès vers la page Edit pour l'item.
- Afficher le formulaire de suppression.
import { Link } from "react-router";
import { useItems } from "./hooks";
import ItemDeleteForm from "./ItemDeleteForm";
function ItemShow() {
const { item } = useItems();
if (item == null) {
throw new Error("404");
}
return (
<>
<h1>{item.title}</h1>
<Link to={`/items/${item.id}/edit`}>Modifier</Link>
<ItemDeleteForm />
</>
);
}
export default ItemShow;Note
useItems lit l'id dans l'URL avec useParams et fournit l'item correspondant.
Objectifs :
- Afficher le formulaire de suppression.
- Déclencher l'action de suppression à la soumission.
import { useItems } from "./hooks";
function ItemDeleteForm() {
const { deleteItem } = useItems();
return (
<form action={deleteItem}>
<button type="submit">Supprimer</button>
</form>
);
}
export default ItemDeleteForm;Objectifs pour la page Create :
- Afficher le formulaire.
- Créer un nouvel item "vide".
import { useItems } from "./hooks";
import ItemForm from "./ItemForm";
function ItemCreate() {
const { addItem } = useItems();
const newItem = {
title: "",
};
return (
<ItemForm defaultValue={newItem} action={addItem}>
<button type="submit">Ajouter</button>
</ItemForm>
);
}
export default ItemCreate;Objectifs pour la page Edit :
- Afficher le formulaire.
- Passer l'item existant fourni par
useItems.
import { useItems } from "./hooks";
import ItemForm from "./ItemForm";
function ItemEdit() {
const { item, editItem } = useItems();
if (item == null) {
throw new Error("404");
}
return (
<ItemForm defaultValue={item} action={editItem}>
<button type="submit">Modifier</button>
</ItemForm>
);
}
export default ItemEdit;Le même composant ItemForm est utilisé pour les 2 pages. Les props permettent de :
- l'alimenter avec un nouvel item ou un item existant,
- choisir l'action à déclencher à la soumission.
import { type PropsWithChildren, useId } from "react";
interface ItemFormProps extends PropsWithChildren {
defaultValue: Omit<Item, "id" | "user_id">;
action: (partialItem: Omit<Item, "id" | "user_id">) => void;
}
function ItemForm({ children, defaultValue, action }: ItemFormProps) {
const titleId = useId();
return (
<form
action={(formData) => {
const title = formData.get("title")?.toString() ?? "";
// Data should be validated here
action({ title });
}}
>
<p>
<label htmlFor={titleId}>title</label>
<input
id={titleId}
type="text"
name="title"
defaultValue={defaultValue.title}
/>
</p>
{children}
</form>
);
}
export default ItemForm;Ce pattern rend le code plus réutilisable : un seul composant gère la saisie et la soumission, quelle que soit la page où il est utilisé.