<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Virgile RIETSCH</title>
    <description>The latest articles on Forem by Virgile RIETSCH (@varkoff).</description>
    <link>https://forem.com/varkoff</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F473835%2F617c0546-3f0f-4daf-aaed-6e085de1876b.jpg</url>
      <title>Forem: Virgile RIETSCH</title>
      <link>https://forem.com/varkoff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/varkoff"/>
    <language>en</language>
    <item>
      <title>Comment bien gérer les erreurs avec Remix ? (ErrorBoundary)</title>
      <dc:creator>Virgile RIETSCH</dc:creator>
      <pubDate>Sat, 25 May 2024 09:59:49 +0000</pubDate>
      <link>https://forem.com/varkoff/comment-bien-gerer-les-erreurs-avec-remix-errorboundary-1m6m</link>
      <guid>https://forem.com/varkoff/comment-bien-gerer-les-erreurs-avec-remix-errorboundary-1m6m</guid>
      <description>&lt;p&gt;As-tu déjà eu des erreurs Javascript en production ? &lt;strong&gt;Pire&lt;/strong&gt; encore, as-tu reçu l'appel d'un client, qui se plaint d'avoir reçu un message d'erreur ?&lt;br&gt;
Tu perds de la crédibilité.&lt;/p&gt;

&lt;p&gt;Heureusement, il existe le composant &lt;em&gt;ErrorBoundary&lt;/em&gt; qui permet d'afficher un composant d'erreur, respectant le thème du site et ton Design System.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ZcOywCf4shU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Avant d'utiliser ErrorBoundary
&lt;/h2&gt;

&lt;p&gt;Tu retrouves un message d'erreur générique incompréhensible. Difficile de comprendre ce qui a pu se passer, et tu vas prendre du temps à réparer le bug. De plus, ton site est inutilisable en l'état. La navigation de ton utilisateur a été coupée brutalement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2F3_Ul0_S_e9785a9f36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2F3_Ul0_S_e9785a9f36.png" alt="Erreur générique Javascript"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Après avoir mis en place ErrorBoundary
&lt;/h2&gt;

&lt;p&gt;Tu améliores l'expérience utilisateur. Tu réduis l'impact de l'erreur à l'endroit où elle a été déclenchée. Cela permet au développeur de vite résoudre le problème, car il sait exactement de quel fichier il s'agit.&lt;/p&gt;

&lt;p&gt;Dans Remix, on utilise le composant &lt;a href="https://remix.run/docs/en/main/route/error-boundary" rel="noopener noreferrer"&gt;ErrorBoundary&lt;/a&gt;, qui va remplacer le composant ayant déclenché l'erreur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ferror_boundary_cdcf793439.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ferror_boundary_cdcf793439.png" alt="Message d'erreur Remix localisé, clair et concis, ne perturbant pas le parcours utilisateur"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tu retrouves cet exemple et davantage d'information dans la documentation de Remix concernant &lt;a href="https://remix.run/docs/en/main/guides/errors" rel="noopener noreferrer"&gt;l'error handling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour améliorer l'expérience utilisateur (UX), il est recommandé d'offrir une explication claire dans le message d'erreur, comme expliqué dans la figure ci-dessous (&lt;a href="https://www.linkedin.com/pulse/designing-better-error-messages-ux-vitaly-friedman/" rel="noopener noreferrer"&gt;Source&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2F1692716312806_e0b0f10037.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2F1692716312806_e0b0f10037.png" alt="Erreur Javascript personnalisée, rassurant le client et expliquant ce qui s'est passé"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un message d'erreur idéal :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;rassure&lt;/strong&gt; le client&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;explique&lt;/strong&gt; la raison du problème&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;offre&lt;/strong&gt; une marche à suivre pour arranger les choses (contacter le support)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Qu'est-ce que le composant ErrorBoundary ?
&lt;/h2&gt;

&lt;p&gt;C'est une &lt;em&gt;librairie&lt;/em&gt; Javascript, disponible sur &lt;em&gt;NPM&lt;/em&gt; sous le nom de &lt;a href="https://www.npmjs.com/package/react-error-boundary" rel="noopener noreferrer"&gt;react-error-boundary&lt;/a&gt;. Elle est téléchargée plus de 3,7 millions de fois par semaine.&lt;/p&gt;

&lt;p&gt;Pour l'utiliser dans une application React standard, il suffit de la télécharger dans ton projet :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-error-boundary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensuite, tu l'utilises dans un composant React. Elle va &lt;em&gt;englober&lt;/em&gt; le composant qui risque de déclencher une erreur, et afficher un composant &lt;em&gt;fallback&lt;/em&gt; à la place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-error-boundary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Something went wrong&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ExampleApplication&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cet exemple montre deux composants :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Le composant parent &lt;em&gt;ErrorBoundary&lt;/em&gt;, qui prend un &lt;em&gt;children&lt;/em&gt; (le composant enfant) et une propriété &lt;em&gt;fallback&lt;/em&gt; (affichant le composant d'erreur)&lt;/li&gt;
&lt;li&gt;Le composant enfant &lt;em&gt;ExampleApplication&lt;/em&gt;. En cas d'erreur de ce dernier, l'erreur ne va pas remonter à la racine de l'application. À la place, l'erreur va afficher le composant &lt;em&gt;fallback&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il y a une deuxième manière d'utiliser ce composant, offrant plus de contrôle et une meilleure UX.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-error-boundary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fallbackRender&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resetErrorBoundary&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Call resetErrorBoundary() to reset the error boundary and retry the render.&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'alert'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Something went wrong:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt;
    &lt;span class="na"&gt;fallbackRender&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fallbackRender&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;onReset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Reset the state of your app so the error doesn't happen again&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ExampleApplication&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La propriété &lt;em&gt;fallbackRender&lt;/em&gt; accepte une fonction fléchée, nous permettant de récupérer deux arguments :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Les informations sur l'erreur qui a été déclenchée&lt;/li&gt;
&lt;li&gt;Une fonction permettant de ré-effectuer un rendu du composant. Déclencher cette fonction permet d'annuler l'erreur, et d'afficher le composant enfant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Généralement, on laisse l'utilisateur appeler cette méthode &lt;code&gt;resetErrorBoundary&lt;/code&gt;. Plus haut, par exemple en utilisant le bouton &lt;em&gt;Try Again&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Mais cet article ne se concentre pas sur ce composant très utile.&lt;/p&gt;

&lt;p&gt;Si tu utilises &lt;a href="https://remix.run" rel="noopener noreferrer"&gt;Remix&lt;/a&gt;, tu n'as même pas besoin de l'utiliser.&lt;/p&gt;

&lt;p&gt;Il est intégré directement dans la librairie !&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment gérer tes erreurs avec Remix
&lt;/h2&gt;

&lt;p&gt;Pour déclencher une erreur dans ton application Remix, il te suffit d'ajouter l'instruction &lt;code&gt;throw new Error('bam')&lt;/code&gt; sur n'importe quelle route active.&lt;/p&gt;

&lt;p&gt;Ce qui affiche ceci :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Fimage_19_c7ce872c07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Fimage_19_c7ce872c07.png" alt="Une erreur dans une application Remix"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cette erreur n'est pas claire du tout. Et elle rend ton application inutilisable pour les clients : Ils ne peuvent pas accéder à une autre page depuis l'interface.&lt;/p&gt;

&lt;p&gt;De plus, cela ne t'aide pas à connaître la raison de l'erreur : elle pourrait être déclenchée par n'importe quoi.&lt;/p&gt;

&lt;p&gt;Comment faire ? Il suffit d' &lt;strong&gt;exporter un composant nommé ErrorBoundary&lt;/strong&gt; dans ton application Remix. Commence par l'importer dans le fichier &lt;code&gt;root.tsx&lt;/code&gt;, se situant dans le dossier &lt;code&gt;app/routes&lt;/code&gt; de ton projet Remix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le composant ErrorBoundary (Remix)
&lt;/h2&gt;

&lt;p&gt;Le composant &lt;em&gt;ErrorBoundary&lt;/em&gt; est une convention de Remix (au même titre que les méthodes &lt;em&gt;loader&lt;/em&gt; et &lt;em&gt;action&lt;/em&gt;). Exporter un composant nommé "ErrorBoundary" dans n'importe quelle route indique à Remix qu'en cas d'erreur, il doit afficher son contenu, et ne pas propager l'erreur plus haut.&lt;/p&gt;

&lt;p&gt;Prenons par exemple l'export ci-dessous :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ErrorBoundary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouteError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Une erreur inattendue est survenue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Après avoir ajouté ces quelques lignes, notre application ressemble à ça :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Fimage_20_66ae48cf4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Fimage_20_66ae48cf4e.png" alt="Notre application Remix affiche le message d'erreur "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On avait rajouté la ligne &lt;code&gt;throw new Error('bam');&lt;/code&gt;. Au lieu d'afficher l'erreur en rouge, sans design, notre composant &lt;em&gt;ErrorBoundary&lt;/em&gt; est affiché.&lt;/p&gt;

&lt;p&gt;Cela nous apporte quelques bénéfices :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L'erreur ne se propage pas au dessus (on y revient un peu plus bas)&lt;/li&gt;
&lt;li&gt;On peut designer notre message d'erreur, expliquer au client ce qui s'est passé, et lui indiquer la marche à suivre&lt;/li&gt;
&lt;li&gt;En bonus, on peut sauvegarder cette erreur avec un outil comme &lt;em&gt;Sentry&lt;/em&gt;, ou l'envoyer par email pour être prévenu si jamais elle est déclenchée en production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Les erreurs ne remontent pas
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://react.dev" rel="noopener noreferrer"&gt;React&lt;/a&gt; utilise un système de composants, avec des balises imbriquées. Ce qui donne une hiérarchie avec des "parents" et des "enfants".&lt;/p&gt;

&lt;p&gt;Avec &lt;strong&gt;Remix&lt;/strong&gt;, il est possible d'implémenter des &lt;em&gt;routes imbriquées&lt;/em&gt;. Cela signifie qu'il est possible d'avoir plusieurs routes &lt;em&gt;actives&lt;/em&gt; en même temps.&lt;/p&gt;

&lt;p&gt;C'est ce que nous montre l'image, que nous avions vu au dessus :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ferror_boundary_cdcf793439.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ferror_boundary_cdcf793439.png" alt="Message d'erreur Remix localisé, clair et concis, ne perturbant pas le parcours utilisateur"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cette image représente une route imbriquée. Quatres routes sont actuellement actives :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La route &lt;em&gt;root&lt;/em&gt; principale. C'est notre composant d'entrée, cette route est rendue sur toutes les routes. La route &lt;em&gt;root&lt;/em&gt; affiche la barre latérale, sur le côté gauche.&lt;/li&gt;
&lt;li&gt;La route &lt;em&gt;sales&lt;/em&gt; (on la voit comme premier segment dans l'URL) affiche le titre de la page &lt;strong&gt;"Sales"&lt;/strong&gt;, ainsi qu'une barre de navigation horizontale.&lt;/li&gt;
&lt;li&gt;La route &lt;em&gt;invoices&lt;/em&gt; (on retrouve son segment dans l'URL) est "enfant" de la route Sales, et affiche les statistiques globales, la barre jaune et verte ainsi que le tableau de factures&lt;/li&gt;
&lt;li&gt;La route dynamique &lt;em&gt;102000&lt;/em&gt; (où &lt;strong&gt;102000&lt;/strong&gt; représente une &lt;em&gt;id&lt;/em&gt; de facture) est imbriquée dans &lt;em&gt;invoices&lt;/em&gt; (encore une fois, on le constate grâce au segment d'URL final). C'est le dernier enfant de cette hiérarchie, l'enfant le plus bas. Et il est en erreur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On peut symboliser cette vue d'une autre manière :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Sales&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Invoices&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Invoice&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"102000"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Invoices&lt;/span&gt;
  &lt;span class="err"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="na"&gt;Sales&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chaque imbrication représente un enfant dans notre composant. Et le composant &lt;strong&gt;ErrorBoundary&lt;/strong&gt; empêche notre erreur de remonter plus haut.&lt;/p&gt;

&lt;p&gt;Ce qui est représenté par l'image ci-dessus, au final, est un message d'erreur qui a empêché une erreur déclenchée dans le composant &lt;code&gt;Invoice&lt;/code&gt; de remonter plus haut.&lt;/p&gt;

&lt;p&gt;C'est un peu comme s'il s'était passé la chose suivante :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Sales&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Invoices&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Something went wrong'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Invoice&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'102000'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ErrorBoundary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Invoices&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Sales&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remix a remplacé le composant &lt;code&gt;Invoice&lt;/code&gt; par un composant &lt;code&gt;Error&lt;/code&gt;, car ce fichier était protégé par une fonction ErrorBoundary.&lt;/p&gt;

&lt;p&gt;En ajoutant ce composant sur notre route imbriquée, nous améliorons considérablement l'expérience de nos utilisateurs. De plus, nos développeurs identifient plus rapidement le problème, car le composant en erreur est au plus bas dans la hiérarchie.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>remix</category>
    </item>
    <item>
      <title>Héberge des fichiers avec Remix</title>
      <dc:creator>Virgile RIETSCH</dc:creator>
      <pubDate>Mon, 20 May 2024 07:05:54 +0000</pubDate>
      <link>https://forem.com/varkoff/heberge-des-fichiers-avec-remix-5hb4</link>
      <guid>https://forem.com/varkoff/heberge-des-fichiers-avec-remix-5hb4</guid>
      <description>&lt;p&gt;Dans cet article, nous allons implémenter ensemble un &lt;a href="https://algomax.fr/blog/implemente-ton-premier-formulaire-avec-remix-full-typesafe-ux-friendly"&gt;formulaire&lt;/a&gt; permettant d'héberger des fichiers (images, vidéos, PDFs...). Nous utilisons le framework &lt;a href="https://remix.run"&gt;Remix&lt;/a&gt; et son &lt;a href="https://algomax.fr/blog/les-6-routes-a-connaitre-avec-le-framework-remix-framework-react"&gt;puissant système de routing&lt;/a&gt; pour y parvenir.&lt;/p&gt;

&lt;p&gt;Vous pouvez aussi consulter ce &lt;a href="https://youtu.be/c7nLBwfg_K8"&gt;guide au format vidéo&lt;/a&gt; sur YouTube.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/c7nLBwfg_K8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Voici la commande pour initialiser un nouveau projet Remix :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-remix@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comment héberger un formulaire avec Remix ?
&lt;/h2&gt;

&lt;p&gt;On a besoin de trois éléments : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;un fichier à héberger&lt;/li&gt;
&lt;li&gt;un &lt;code&gt;input&lt;/code&gt; de type &lt;code&gt;file&lt;/code&gt; pour le sélectionner et l'envoyer au serveur&lt;/li&gt;
&lt;li&gt;un serveur pour le sauvegarder et le &lt;em&gt;servir&lt;/em&gt; sur une route&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Un fichier à héberger
&lt;/h3&gt;

&lt;p&gt;Voici une image. Je vous laisse la &lt;a href="https://unsplash.com/fr/photos/disque-vinyle-noir-et-blanc-hrUhyFq6u-A"&gt;télécharger&lt;/a&gt;. C'est le document que nous allons héberger ensemble.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wk1_TTUl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/brett_jordan_hr_Uhy_Fq6u_A_unsplash_3698c66818.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wk1_TTUl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/brett_jordan_hr_Uhy_Fq6u_A_unsplash_3698c66818.jpg" alt="un vinyle, l'emoji utilisé par l'équipe de développement de Remix" width="640" height="647"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Un input de type file pour envoyer le fichier au serveur
&lt;/h3&gt;

&lt;p&gt;Nous allons ajouter un fichier dans le dossier &lt;code&gt;app/routes&lt;/code&gt;, nommé &lt;code&gt;file&lt;/code&gt;. Dedans, nous allons exporter par défaut un composant React (ce sera notre vue).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'mt-8 flex flex-col gap-2 w-full items-center'&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Soumettre&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notez l'utilisation du composant &lt;a href="https://remix.run/docs/en/main/components/form"&gt;Form&lt;/a&gt; de Remix. Bien que l'hébergement des fichiers fonctionne avec un &lt;a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/form"&gt;formulaire classique&lt;/a&gt;, il est recommendé de l'utiliser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un serveur pour sauvegarder le fichier et le servir aux utilisateurs
&lt;/h3&gt;

&lt;p&gt;Dans l'article &lt;a href="https://algomax.fr/blog/les-6-routes-a-connaitre-avec-le-framework-remix-framework-react"&gt;6 Routes à connaître si tu utilises Remix (guide complet)&lt;/a&gt;, nous avons vu ensemble qu'il nous suffit d'ajouter une fonction à notre fichier pour ajouter une logique côté serveur.&lt;/p&gt;

&lt;p&gt;Comme le formulaire effectue un &lt;code&gt;POST&lt;/code&gt;, nous allons ajouter une fonction nommée &lt;a href="https://remix.run/docs/en/main/route/action"&gt;action&lt;/a&gt; dans notre composant, et nous allons l'exporter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Nous devons sauvegarder le fichier à cet endroit&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'mt-8 flex flex-col gap-2 w-full items-center'&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Soumettre&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il ne nous reste plus qu'à coder la logique dans notre &lt;code&gt;action&lt;/code&gt; et nous avons terminé ! N'est-ce pas ?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pas vraiment. Avez-vous entendu parlé de la propriété &lt;code&gt;encType&lt;/code&gt; ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  La propriété de formulaire 'encType'
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Version courte
&lt;/h3&gt;

&lt;p&gt;Il faut rajouter la propriété &lt;code&gt;encType&lt;/code&gt; à notre formulaire.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Nous devons sauvegarder le fichier à cet endroit&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;
            &lt;span class="na"&gt;encType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'multipart/form-data'&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'mt-8 flex flex-col gap-2 w-full items-center'&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Soumettre&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pourquoi rajouter la propriété encType ?
&lt;/h3&gt;

&lt;p&gt;Je ne connaissais pas cette propriété avant d'en avoir besoin. Par défaut, la propriété &lt;a href="https://developer.mozilla.org/fr/docs/Web/API/HTMLFormElement/enctype"&gt;encType&lt;/a&gt; (ou type d'encodage) prend comme valeur &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;. Mais il en existe deux autres.&lt;/p&gt;

&lt;p&gt;Voici la &lt;a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/form#enctype"&gt;définition sur MDN&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lorsque la valeur de l'attribut method est post, cet attribut définit le &lt;a href="https://fr.wikipedia.org/wiki/Type_de_m%C3%A9dias"&gt;type MIME&lt;/a&gt; qui sera utilisé pour encoder les données envoyées au serveur. C'est un attribut énuméré qui peut prendre les valeurs suivantes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;: la valeur par défaut si l'attribut n'est pas défini&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;multipart/form-data&lt;/code&gt; : la valeur utilisée par un élément &lt;a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/input"&gt;input&lt;/a&gt; avec l'attribut type="file".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;text/plain&lt;/code&gt;, correspondant au &lt;a href="https://fr.wikipedia.org/wiki/Type_de_m%C3%A9dias"&gt;type MIME&lt;/a&gt; éponyme et utilisé à des fins de débogage.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous utilisons un &lt;code&gt;input&lt;/code&gt; de type &lt;code&gt;file&lt;/code&gt;. Nous avons donc besoin de rajouter la propriété &lt;code&gt;encType='multipart/form-data'&lt;/code&gt; pour envoyer notre fichier au format binaire.&lt;/p&gt;

&lt;p&gt;Nous avons terminé l'implémentation côté client ! Le reste se passe côté serveur. &lt;/p&gt;

&lt;h2&gt;
  
  
  Sauvegarder un fichier côté serveur
&lt;/h2&gt;

&lt;p&gt;Pour pouvoir sauvegarder notre fichier et la servir à nos utilisateurs, nous allons devoir&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;l'extraire du &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData"&gt;FormData&lt;/a&gt; avec les API de Remix &lt;a href="https://remix.run/docs/en/main/utils/unstable-create-file-upload-handler"&gt;
unstable_createFileUploadHandler&lt;/a&gt; et &lt;a href="https://remix.run/docs/en/main/utils/parse-multipart-form-data#unstable_parsemultipartformdata"&gt;
unstable_parseMultipartFormData&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;(Bonus) Valider le &lt;code&gt;formData&lt;/code&gt; avec Zod et Conform&lt;/li&gt;
&lt;li&gt;(Bonus) Renommer le fichier et lui donner un identifiant unique&lt;/li&gt;
&lt;li&gt;Servir le fichier aux utilisateurs (le rendre disponible à l'URL &lt;code&gt;localhost:3000/files/image.png&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Extraire le fichier depuis le FormData
&lt;/h3&gt;

&lt;p&gt;Nous souhaitons conserver le document sur notre serveur. Pour ce faire, nous allons utiliser la méthode &lt;a href="https://remix.run/docs/en/main/utils/unstable-create-file-upload-handler"&gt;&lt;br&gt;
unstable_createFileUploadHandler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cette méthode prend un objet d'options en argument. Voici les options que nous allons utiliser :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;maxPartSize&lt;/code&gt; pour définir la taille max du fichier en bytes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;directory&lt;/code&gt; pour définir le dossier de sauvegarde du document
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;unstable_createFileUploadHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;unstable_parseMultipartFormData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;unstable_parseMultipartFormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;unstable_createFileUploadHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;maxPartSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 10MB&lt;/span&gt;
            &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 👈 notre fichier au format Buffer&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 👈 Le nom du fichier pour pouvoir le retrouver sur le serveur&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;
            &lt;span class="na"&gt;encType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'multipart/form-data'&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'mt-8 flex flex-col gap-2 w-full items-center'&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Soumettre&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Enregistrer le fichier sur le disque dur serveur
&lt;/h3&gt;

&lt;p&gt;Au moment de récupérer le fichier, ligne &lt;code&gt;16&lt;/code&gt;, le fichier a déjà été enregistré dans le dossier &lt;code&gt;uploads&lt;/code&gt;. Pour avoir plus de contrôle sur la sauvegarde de ce fichier, je préfère utiliser la méthode &lt;a href="https://remix.run/docs/en/main/utils/unstable-create-memory-upload-handler"&gt;&lt;br&gt;
unstable_createMemoryUploadHandler&lt;/a&gt;. Cela nous permet d'enregistrer nous-même le fichier récupéré ligne &lt;code&gt;17&lt;/code&gt; (on peut ensuite l'envoyer sur AWS S3 ou un autre service ...)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;unstable_createMemoryUploadHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;unstable_parseMultipartFormData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;saveVideoToLocal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/videos.server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;unstable_parseMultipartFormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;unstable_createMemoryUploadHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;maxPartSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 10MB&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 👈 notre fichier au format Buffer&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 👈 Le nom du fichier pour pouvoir le retrouver sur le serveur&lt;/span&gt;
    &lt;span class="c1"&gt;// mark&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveVideoToLocal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;videoFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// 👈 On sauvegarde le fichier sur le serveur&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;
            &lt;span class="na"&gt;encType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'multipart/form-data'&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'mt-8 flex flex-col gap-2 w-full items-center'&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'file'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Soumettre&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveVideoToLocal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;videoFile&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;videoFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;videoFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseDirectory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;originalName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;videoFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrayBufferView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileExists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;checkIfFileExists&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileExists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le fichier existe déjà ...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseDirectory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrayBufferView&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;originalName&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Servir le fichier aux utilisateurs
&lt;/h3&gt;

&lt;p&gt;Pour rendre accessible nos fichiers hébergés (par exemple à l'URL &lt;code&gt;localhost:3000/file/image.jpeg&lt;/code&gt;, nous avons besoin de :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Créer une &lt;strong&gt;nouvelle route&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Vérifier que le fichier &lt;strong&gt;existe&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Le renvoyer en fonction de son &lt;strong&gt;mimetype&lt;/strong&gt; (si c'est un &lt;em&gt;JPEG&lt;/em&gt;, la réponse sera différente par rapport au format &lt;em&gt;mp4&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous allons créer un nouveau fichier nommé &lt;code&gt;file.$filename.tsx&lt;/code&gt; dans le dossier &lt;code&gt;app/routes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour ce faire, nous devons également installer la librairie &lt;a href="https://npmjs.com/package/mime"&gt;mime&lt;/a&gt;. Les autres librairies &lt;code&gt;fs&lt;/code&gt; et &lt;code&gt;path&lt;/code&gt; sont natives à l'environnement NodeJS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoaderFunctionArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mime&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;LoaderFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 👈 On vérifie que le nom du fichier est bien fourni&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No filename provided&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 👈 On construit le chemin absolu du fichier&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 👈 On lit le fichier&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mimeType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 👈 On récupère le type MIME du fichier&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 👈 On renvoie le fichier&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mimeType&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/octet-stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 👈 On renvoie le type MIME du fichier,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Conclusion&lt;br&gt;
Dans cet article, nous avons vu comment implémenter un formulaire d'upload de fichiers dans Remix. Nous avons utilisé le composant &lt;a href="https://remix.run/docs/en/main/components/form"&gt;Form&lt;/a&gt;  de Remix et les API unstable_createFileUploadHandler et unstable_parseMultipartFormData pour gérer le transfert de fichiers. Nous avons également vu comment sauvegarder les fichiers sur le serveur et les servir aux utilisateurs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>remix</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Développe et valide un formulaire avec Remix</title>
      <dc:creator>Virgile RIETSCH</dc:creator>
      <pubDate>Sun, 18 Feb 2024 10:06:07 +0000</pubDate>
      <link>https://forem.com/varkoff/developpe-et-valide-un-formulaire-avec-remix-6g</link>
      <guid>https://forem.com/varkoff/developpe-et-valide-un-formulaire-avec-remix-6g</guid>
      <description>&lt;p&gt;Votre lead dev vous demande d'améliorer l'UX du formulaire de contact pour qu'il soit plus explicite dans l'affichage de ses erreurs. Il est vrai que l'UX est importante, même en tant que développeur. J'aime personnellement savoir quel champ a posé problème lorsque j'envoie un formulaire. Et les utilisateurs aussi : ils peuvent gagner en autonomie et nous expliquer l'erreur qu'ils ont rencontré.&lt;/p&gt;

&lt;p&gt;Dans cet article, nous allons implémenter étape par étape un formulaire avec le framework &lt;a href="https://remix.run" rel="noopener noreferrer"&gt;Remix&lt;/a&gt;. D'abord sans librairie externe. Puis nous allons l'améliorer peu à peu en ajoutant &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, puis &lt;a href="https://conform.guide/" rel="noopener noreferrer"&gt;Conform&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cet article existe également au &lt;a href="https://youtu.be/_-ZOGj80TMw" rel="noopener noreferrer"&gt;format vidéo&lt;/a&gt; sur YouTube.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/_-ZOGj80TMw"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Commençons par faire simple.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implémentation d'un formulaire basique avec Remix
&lt;/h2&gt;

&lt;p&gt;Avant d'installer quoi que ce soit, nous allons implémenter un formulaire HTML basique. &lt;/p&gt;

&lt;p&gt;Nous allons d'abord :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instantier un nouveau projet Remix avec la commande &lt;code&gt;npx create-remix@latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm install&lt;/code&gt; pour installer les dépendances&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run dev&lt;/code&gt; pour lancer le serveur de développement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ensuite, nous allons supprimer le contenu du fichier &lt;code&gt;app/routes/_index.tsx&lt;/code&gt; et le remplacer par un formulaire &lt;em&gt;simple&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actuellement, nous avons implémenté un formulaire HTML qui contient un &lt;code&gt;input&lt;/code&gt; et un &lt;code&gt;button&lt;/code&gt;. Il ne nous sera pas très utile dans cet état.&lt;br&gt;
Ce formulaire utilise la méthode &lt;em&gt;GET&lt;/em&gt; par défaut. Cela signifie que chacune des données présente dans l'&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input" rel="noopener noreferrer"&gt;input&lt;/a&gt; seront visibles dans l'URL lors de la soumission (sous forme de &lt;a href="https://developer.mozilla.org/fr/docs/Web/API/URLSearchParams" rel="noopener noreferrer"&gt;paramètres d'URL&lt;/a&gt;). De plus, l'&lt;code&gt;input&lt;/code&gt; ne possède pas de propriété &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#name" rel="noopener noreferrer"&gt;&lt;code&gt;name&lt;/code&gt;&lt;/a&gt;. Sa valeur ne sera pas récupérée. Elle a besoin d'avoir un nom pour être identifiée. (comme la paire clé-valeur dans un &lt;em&gt;object&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Cet article va uniquement utiliser un formulaire &lt;a href="https://developer.mozilla.org/fr/docs/Web/HTTP/Methods/POST" rel="noopener noreferrer"&gt;POST&lt;/a&gt;, qui est idéal pour envoyer des données sensibles comme des adresses email.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rajoutons des attributs 'form.POST' et 'input.name'
&lt;/h3&gt;

&lt;p&gt;Il manque des attributs à notre formulaire: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;la méthode &lt;code&gt;POST&lt;/code&gt; de notre formulaire JSX&lt;/li&gt;
&lt;li&gt;la propriété &lt;code&gt;name&lt;/code&gt; à notre &lt;code&gt;input&lt;/code&gt; (qui prend également la valeur &lt;em&gt;name&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Essayons de soumettre notre formulaire !&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ffailed_submission_402df7004f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ffailed_submission_402df7004f.gif" alt="soumission de formulaire échouée"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  405 Method Not Allowed
&lt;/h3&gt;

&lt;p&gt;L'erreur &lt;code&gt;405 Method Not Allowed&lt;/code&gt; apparaît. Mais nous n'avons pas fait d'erreur au niveau de l'implémentation du formulaire. Cette erreur nous explique qu'il faut maintenant ouvrir un &lt;em&gt;API endpoint&lt;/em&gt; à cette &lt;em&gt;URL&lt;/em&gt;. Ce que j'appelle &lt;em&gt;API endpoint&lt;/em&gt; est en réalité une déclaration de route &lt;em&gt;API&lt;/em&gt;, comme avec Express.&lt;/p&gt;

&lt;p&gt;La page &lt;a href="https://remix.run/docs/en/main/guides/api-routes#api-routes" rel="noopener noreferrer"&gt;API Routes&lt;/a&gt; (documentation de Remix) nous explique que les fichiers de type route (dans le dossier &lt;code&gt;routes&lt;/code&gt;) sont leur propre API. &lt;/p&gt;

&lt;p&gt;Pour mieux comprendre, regardons la page &lt;a href="https://remix.run/docs/en/main/guides/api-routes#api-routes" rel="noopener noreferrer"&gt;Fullstack Data Flow&lt;/a&gt; de la doc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Floader_action_component_a52cbe146c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Floader_action_component_a52cbe146c.png" alt="Lifecycle Remix - Loader, Component, Action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le schéma ci-dessus représente l'ordre d'exécution des méthodes de Remix :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La méthode &lt;code&gt;loader&lt;/code&gt; (facultative) s'éxecute en premier lors d'un chargement de page. Exécuté &lt;strong&gt;côté serveur&lt;/strong&gt;, on peut charger les données nécessaires avant d'afficher la vue. Pratique pour implémenter le &lt;em&gt;SSR&lt;/em&gt; et très pratique pour le &lt;strong&gt;SEO&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Le composant &lt;a href="https://fr.react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt; exporté &lt;strong&gt;par défaut&lt;/strong&gt; représente la vue. Il s'éxecute à la fin de la méthode &lt;code&gt;loader&lt;/code&gt;. Ce composant est exécuté côté serveur puis &lt;strong&gt;côté client&lt;/strong&gt;. C'est dedans qu'on va ajouter nos formulaires, nos states (&lt;em&gt;useState&lt;/em&gt;, &lt;em&gt;useEffect&lt;/em&gt;, etc ...)&lt;/li&gt;
&lt;li&gt;La méthode &lt;code&gt;action&lt;/code&gt; (facultative) s'éxecute &lt;strong&gt;seulement&lt;/strong&gt; si une soumission de formulaire &lt;strong&gt;POST&lt;/strong&gt; est effectuée dans la route. Cette méthode contient toute la logique liée à notre mutation (se connecter, s'inscrire, modifier une donnée en DB ...) et va s'exécuter &lt;strong&gt;côté serveur&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;405 Method Not Allowed signifie qu'on effectue un &lt;strong&gt;POST&lt;/strong&gt; sur notre route, mais qu'on a oublié d'implémenter la logique &lt;strong&gt;serveur&lt;/strong&gt; (on doit déclarer une méthode &lt;code&gt;action&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Ajout de la méthode 'action'
&lt;/h3&gt;

&lt;p&gt;La fonction &lt;a href="https://remix.run/docs/en/main/route/action" rel="noopener noreferrer"&gt;action&lt;/a&gt; est une convention de Remix. Ajoutons cette méthode pour autoriser les requêtes &lt;code&gt;POST&lt;/code&gt; dans notre route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'submit'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'erreur est réparée. On peut passer à la suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation d'un formulaire côté serveur
&lt;/h2&gt;

&lt;p&gt;Soumettre notre formulaire (en cliquant sur le &lt;code&gt;button&lt;/code&gt;) effectue un &lt;code&gt;POST&lt;/code&gt; sur notre route, et va déclencher la méthode &lt;code&gt;action&lt;/code&gt;. Cette méthode ne fait rien à part renvoyer &lt;code&gt;null&lt;/code&gt;. &lt;br&gt;
Nous allons ajouter un peu de logique pour :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extraire les données du formulaire&lt;/li&gt;
&lt;li&gt;les valider manuellement&lt;/li&gt;
&lt;li&gt;envoyer un retour à l'utilisateur en cas d'erreur&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Récupération des données de formulaire côté serveur
&lt;/h3&gt;

&lt;p&gt;Avant de valider les données côté serveur, nous devons les récupérer.&lt;br&gt;
Voici la solution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explications :&lt;/p&gt;

&lt;p&gt;Au moment du &lt;code&gt;POST&lt;/code&gt;, notre formulaire va récupérer chaque valeur de ses &lt;code&gt;input&lt;/code&gt; &lt;strong&gt;possédant la propriété &lt;code&gt;name&lt;/code&gt;&lt;/strong&gt;. Je reformule. Si votre &lt;code&gt;input&lt;/code&gt; n'a pas de propriété &lt;code&gt;name&lt;/code&gt;, sa valeur ne pourra être récupérée côté serveur. C'est un pré-requis. Cette propriété permet d'identifier l'&lt;code&gt;input&lt;/code&gt; côté serveur.&lt;/p&gt;

&lt;p&gt;Ensuite, une requête est effectuée à notre action avec les données de notre formulaire. C'est cette requête que nous récupérons comme argument dans notre &lt;em&gt;action&lt;/em&gt;. La &lt;code&gt;request&lt;/code&gt; possède plusieurs informations, notamment la méthode &lt;code&gt;formData&lt;/code&gt; qui va nous permettre de récupérer les données du formulaire. C'est une promesse, nous devons utiliser &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cette méthode nous renvoie un objet &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; possédant plusieurs méthodes. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Le &lt;code&gt;FormData&lt;/code&gt; n'est pas exclusif à l'environnement serveur. Nous pouvons utiliser cette interface côté client, par exemple en utilisant le props &lt;code&gt;onSubmit&lt;/code&gt; dans notre &lt;code&gt;form&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chaque valeur peut être récupérée en utilisant la méthode &lt;code&gt;.get($key)&lt;/code&gt; de notre &lt;code&gt;FormData&lt;/code&gt;. &lt;strong&gt;$key&lt;/strong&gt; représente la valeur de la propriété &lt;code&gt;name&lt;/code&gt; de l'input. Dans notre cas, nous l'avons appelé &lt;code&gt;name&lt;/code&gt;. Je vous présenterai également un moyen de transformer le &lt;code&gt;formData&lt;/code&gt; en objet clé-valeur (pour ne plus avoir à utiliser &lt;code&gt;formData.get()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Valider les données de formulaire côté serveur
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Pourquoi valider nos données de formulaire ?
&lt;/h4&gt;

&lt;p&gt;Pourquoi valider nos données ? Pour nous assurer que les données reçues sont conformes à ce que nous attendons. Nous allons par exemple valider qu'un numéro de téléphone contient 10 caractères. Sinon, pas la peine de sauvegarder la donnée : On imagine que le numéro est sûrement erroné. &lt;/p&gt;

&lt;p&gt;J'ai déjà eu à valider des adresses emails, des numéro SIRET et des numéros de TVA. Ajouter une étape de validation empêche les farceurs de spam notre base de données avec des valeurs inutilisables.&lt;/p&gt;

&lt;p&gt;Nous pouvons valider notre donnée de plusieurs manières. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;En validant "manuellement" (avec des &lt;code&gt;if&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;En utilisant la librairie &lt;a href="https://www.npmjs.com/package/tiny-invariant" rel="noopener noreferrer"&gt;tiny-invariant&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;En utilisant &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Je n'utilise personnellement jamais la validation manuelle&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Validation manuelle de nos données de formulaire
&lt;/h4&gt;

&lt;p&gt;À titre d'exemple, nous allons interdire l'utilisation des prénoms à moins de 3 caractères.&lt;/p&gt;

&lt;p&gt;Côté serveur, cela implique deux vérifications :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vérifier que la valeur est bien un &lt;code&gt;string&lt;/code&gt; (et pas un fichier)&lt;/li&gt;
&lt;li&gt;Vérifier que le nom fait moins de 3 caractères&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous devons aussi renvoyer au client un message d'erreur ou de succès.&lt;/p&gt;

&lt;p&gt;Côté client, nous faisons quelques modifications :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nous récupérons le message d'erreur ou de succès avec le hook &lt;a href="https://remix.run/docs/en/main/hooks/use-action-data" rel="noopener noreferrer"&gt;useActionData&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Nous l'affichons conditionnellement à l'utilisateur.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useActionData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom est requis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inscription réussie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useActionData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Fsuccessful_feedback_4f841a2ca1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Fsuccessful_feedback_4f841a2ca1.gif" alt="Soumission d'un formulaire avec retour utilisateur sous forme de message"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour un cas d'utilisation très simple comme celui-ci, on peut s'arrêter là. Pas besoin d'installer deux librairies supplémentaires. Cependant, cet exemple ne reflète pas les applications que je pousse en production. Il y a souvent plus de trois champs aux formulaires que je développe. De plus, j'utilise déjà la librairie &lt;strong&gt;zod&lt;/strong&gt; pour valider les données côté serveur (récupérées depuis une API &lt;a href="https://algomax.fr/blog?categories=nestjs" rel="noopener noreferrer"&gt;NestJS&lt;/a&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  Validation de nos données de formulaire avec Zod
&lt;/h4&gt;

&lt;p&gt;Pour pouvoir utiliser &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, (c'est une librairie Javascript de validation de données), nous avons besoin de l'installer dans notre dossier projet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensuite, nous définissons un schéma représentant notre modèle de données. Reprenons l'exemple de notre nom à 3 caractères qui est de type &lt;code&gt;string&lt;/code&gt;. Cela implique une refactorisation du code.&lt;/p&gt;

&lt;p&gt;Nous devons définir le schéma zod en ajoutant les instructions &lt;code&gt;.string()&lt;/code&gt; et &lt;code&gt;.min(3)&lt;/code&gt; (les messages d'erreurs sont facultatifs).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom est requis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensuite, nous allons utiliser la méthode &lt;code&gt;.parse($data)&lt;/code&gt; pour déclencher la validation de notre donnée &lt;code&gt;$data&lt;/code&gt; (ici, ce sera notre &lt;code&gt;name&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voici l'implémentation complète.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useActionData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom est requis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inscription réussie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useActionData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text'&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cette implémentation possède un &lt;strong&gt;avantage&lt;/strong&gt; et un &lt;strong&gt;inconvénient&lt;/strong&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L'avantage c'est la validation Zod qui permet de réutiliser cette logique (en exportant le schéma). C'est simple à maintenir et c'est lisible.&lt;/li&gt;
&lt;li&gt;Le désavantage, c'est qu'en cas d'erreur (si le schéma n'est pas respecté), le message d'erreur de Zod ne sera pas affiché côté client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ffailed_error_handling_97c8579ae5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Falgomax-public.s3.eu-west-3.amazonaws.com%2Ffailed_error_handling_97c8579ae5.gif" alt="La validation Zod échoue, et l'erreur n'est pas attrapée."&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ZodError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;too_small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minimum&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inclusive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;///Users/dev/dev/remix-forms/node_modules/zod/lib/index.mjs:538:31)&lt;/span&gt;
    &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;ZodString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;///Users/dev/dev/remix-forms/node_modules/zod/lib/index.mjs:638:22)&lt;/span&gt;
    &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nf"&gt;action2 &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;///Users/dev/dev/remix-forms/app/routes/_index.tsx:11:32)&lt;/span&gt;
    &lt;span class="s2"&gt;```



Nous avons deux solutions, chacune avec des avantages et des inconvénients.

##### Implémenter nous-même la gestion des erreurs Zod en utilisant 'safeParse'

Cette solution possède un avantage : ne pas installer une librairie supplémentaire.
L'inconvénient, c'est que plus le schéma devient complexe, plus la gestion des erreurs devient complexe

##### Utiliser la librairie Conform pour gérer les erreurs
Cette [librairie](https://conform.guide/) a été conçue spécifiquement pour ce cas d'utilisation (validation de formulaires avec Remix et Zod)

L'avantage principal, c'est que même avec un schéma complexe, le développeur n'aura pas plus de mal à implémenter le formulaire.
Le désavantage, c'est qu'il faut installer une nouvelle librairie. Ça peut être un problème si vous ne voulez pas davantage ralentir votre webapp avec du JS.

Cet article est biaisé, car il parle de l'intégration de Conform dans votre application Remix.

#### Validation des données de formulaire avec Conform

Nous avons besoin d'installer deux librairies pour utiliser Conform.

- `&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;conform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt;&lt;span class="s2"&gt;` pour utiliser son puissant hook `&lt;/span&gt;&lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="s2"&gt;`, valider les données côté client et afficher les erreurs
- `&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;conform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zod&lt;/span&gt;&lt;span class="s2"&gt;` pour valider les données côté serveur et côté client avec Zod. Sans déclencher d'erreur en cas de non-validation.



```&lt;/span&gt;&lt;span class="nx"&gt;bash&lt;/span&gt;
&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;conform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;conform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous allons implémenter Conform en trois étapes. Modifier le schéma Zod (et l'adapter à Conform), ajouter la logique côté client, puis ajouter la logique côté serveur.&lt;/p&gt;

&lt;h5&gt;
  
  
  Adapter le schéma Zod à Conform
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom est requis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bien que ce formulaire ne possède qu'une propriété pour l'instant, Conform s'attend à recevoir un &lt;code&gt;FormData&lt;/code&gt; en paramètres. Il le transforme ensuite en objet. Nous avons déclaré un nouveau &lt;code&gt;Schema&lt;/code&gt; qui est un &lt;code&gt;z.object&lt;/code&gt;, contenant une propriété &lt;code&gt;name&lt;/code&gt; (qui utilise le schéma que nous avons défini toute à l'heure).&lt;/p&gt;

&lt;h5&gt;
  
  
  Ajouter 'useForm' côté client
&lt;/h5&gt;

&lt;p&gt;Conform utilise le schéma Zod pour déterminer les champs de notre formulaire. Notre &lt;code&gt;Schema&lt;/code&gt; possède un champ (&lt;code&gt;name&lt;/code&gt;). Nous utilisons le hook &lt;code&gt;useForm&lt;/code&gt; pour récupérer les &lt;code&gt;props&lt;/code&gt; de notre formulaire et de chacun de nos &lt;code&gt;input&lt;/code&gt; (les &lt;code&gt;fields&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getFormProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getInputProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@conform-to/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getZodConstraint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parseWithZod&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@conform-to/zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom est requis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getZodConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;onValidate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseWithZod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;lastResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getFormProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getInputProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On peut constater qu'on ne définit aucun &lt;code&gt;props&lt;/code&gt; sur nos &lt;code&gt;input&lt;/code&gt;. C'est Conform qui gère leur valeur, leur ID, le focus en cas d'erreur et plein d'autres fonctionnalités lié au &lt;a href="https://remix.run/docs/en/main/discussion/progressive-enhancement" rel="noopener noreferrer"&gt;progressive enhancement&lt;/a&gt; pour offrir à nos utilisateurs une expérience optimale.&lt;/p&gt;

&lt;p&gt;Constatez aussi la méthode &lt;code&gt;parseWithZod&lt;/code&gt;, utilisée pour valider les données du &lt;code&gt;formData&lt;/code&gt; côté client, dans la méthode &lt;code&gt;onValidate&lt;/code&gt;. C'est exactement ce que nous avons fait tout à l'heure côté serveur ! Le &lt;code&gt;FormData&lt;/code&gt; vient du client, et n'est envoyé au serveur qu'après la soumission (et la première validation par Conform et Zod) &lt;/p&gt;

&lt;h5&gt;
  
  
  Validation des données côté serveur avec Conform et 'parseWithZod'
&lt;/h5&gt;

&lt;p&gt;Il nous manque la validation la plus importante. Celle qui est effectuée côté serveur. C'est dans notre action qu'on va vérifier les informations fournies par l'utilisateur. (Est-ce que l'utilisateur existe déjà ? A-t-il validé son mot de passe ? Ces exemples sont utilisés dans toutes les applications, même si notre implémentation ne le reflète pas)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom est requis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parseWithZod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;resetForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous devons toujours récupérer les valeurs du formulaire avec &lt;code&gt;await request.formData()&lt;/code&gt;, sauf que cette fois on laisse Conform se charger de tout, grâce à la méthode &lt;code&gt;parseWithZod&lt;/code&gt;. Cette méthode prend le &lt;code&gt;formData&lt;/code&gt; et notre schéma Zod en deuxième argument.&lt;/p&gt;

&lt;p&gt;Il nous renvoie ensuite un objet &lt;code&gt;submission&lt;/code&gt;, qui possède l'un des deux status : &lt;code&gt;error&lt;/code&gt; et &lt;code&gt;success&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si le statut &lt;code&gt;error&lt;/code&gt; est renvoyé, alors nous renvoyons une réponse Conform au client. Nous devons ensuite la passer comme argument dans le hook &lt;code&gt;useForm&lt;/code&gt; (clé &lt;code&gt;lastResult&lt;/code&gt;) pour qu'il puisse faire le lien entre la soumission qui a échouée côté serveur, et les inputs côté client.&lt;/li&gt;
&lt;li&gt;Si le statut &lt;code&gt;success&lt;/code&gt; est renvoyé, cela signifie que les données sont conformes au schéma. Il n'y a rien de plus à faire. On a implémenté la validation côté serveur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voici le composant intégral (avec le &lt;code&gt;lastResult&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getFormProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getInputProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@conform-to/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getZodConstraint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parseWithZod&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@conform-to/zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useActionData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom est requis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Le nom doit contenir au moins 3 caractères&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nameSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ActionFunctionArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submission&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parseWithZod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;submission&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;resetForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useActionData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getZodConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;onValidate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseWithZod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;lastResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getFormProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;getInputProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;S&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;inscrire&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;En explorant l'implémentation d'un formulaire étape par étape avec &lt;a href="https://remix.run" rel="noopener noreferrer"&gt;Remix&lt;/a&gt;, nous avons découvert l'importance cruciale de l'expérience utilisateur (&lt;em&gt;UX&lt;/em&gt;) dans la gestion des erreurs. Commencer par les fondamentaux nous a permis de comprendre comment récupérer et valider les données de formulaire côté serveur. &lt;/p&gt;

&lt;p&gt;L'utilisation de &lt;a href="https://zod.dev" rel="noopener noreferrer"&gt;Zod&lt;/a&gt; nous a permis de consolider la validation des données, en ajoutant des messages d'erreurs clairs, tout en restant plus simple à maintenir qu'une gestion d'erreur "manuelle".&lt;/p&gt;

&lt;p&gt;Utiliser &lt;a href="https://conform.guide" rel="noopener noreferrer"&gt;Conform&lt;/a&gt; nous a permi d'optimiser l'expérience utilisateur grâce à une validation efficace et des retours visuels, pour prévenir les utilisateurs de leurs erreurs éventuelles avec des messages d'erreurs de qualité.&lt;/p&gt;

&lt;p&gt;Je vous encourage à tester Remix avec ces outils ! Depuis que je les ai découvert, je ne m'en sépare plus.&lt;/p&gt;

&lt;p&gt;Votre feedback est précieux — n'hésitez pas à partager vos expériences, questions ou suggestions sur &lt;a href="https://twitter.com/varkoffs" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; ou sur &lt;a href="https://www.linkedin.com/in/virgile-rietsch/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; si ça vous a été utile. Ensemble, améliorons l'UX de nos formulaires et offrons une expérience agréable à nos utilisateurs.&lt;/p&gt;

</description>
      <category>react</category>
      <category>remix</category>
      <category>zod</category>
      <category>typescript</category>
    </item>
    <item>
      <title>6 Routes à connaître si tu utilises Remix (guide complet)</title>
      <dc:creator>Virgile RIETSCH</dc:creator>
      <pubDate>Tue, 13 Feb 2024 07:21:35 +0000</pubDate>
      <link>https://forem.com/varkoff/6-routes-a-connaitre-si-tu-utilises-remix-guide-complet-3c3l</link>
      <guid>https://forem.com/varkoff/6-routes-a-connaitre-si-tu-utilises-remix-guide-complet-3c3l</guid>
      <description>&lt;p&gt;Envisages-tu de tester ou même d'utiliser le framework &lt;a href="https://remix.run"&gt;Remix&lt;/a&gt; pour ton prochain projet ?&lt;/p&gt;

&lt;p&gt;Pour cela, il va falloir te familiariser avec son puissant &lt;em&gt;routing&lt;/em&gt;. Cet article te liste les &lt;a href="https://www.youtube.com/watch?v=TOMMXhgUQfI"&gt;&lt;strong&gt;6&lt;/strong&gt; types de routes&lt;/a&gt; que tu vas utiliser au quotidien avec Remix. Si tu préfères lire la doc, Remix &lt;a href="https://remix.run/docs/en/main/file-conventions/routes"&gt;explique tout en détail&lt;/a&gt; sur le site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le routing, c'est quoi ?
&lt;/h2&gt;

&lt;p&gt;Commençons par définir ce qu'est le &lt;strong&gt;routing&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;C'est le chemin (sous forme d'URL) qui permet d'accéder à ton site internet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Exemples :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://leboncoin.fr"&gt;leboncoin.fr&lt;/a&gt; te permet d'accéder à la page d'accueil du bon coin, sa route "index".&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://leboncoin.fr/annonces/offres"&gt;leboncoin.fr/annonces/offres&lt;/a&gt; te permet d'accéder à une deuxième page du bon coin. La page est différente de la première, le chemin d'accès dans l'URL est différent. C'est une deuxième route.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tous les sites possèdent minimum une route (qu'on va appeler route &lt;em&gt;index&lt;/em&gt;, ou route &lt;em&gt;par défaut&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Ce concept n'est pas récent. À mes débuts dans le développement, j'intégrais mes premières pages en HTML et PHP. La convention était de nommer nos fichiers &lt;strong&gt;index.html&lt;/strong&gt; et &lt;strong&gt;index.php&lt;/strong&gt;. Ces fichiers étaient ensuite accessibles au nom de domaine local. Par exemple, &lt;code&gt;localhost:8000&lt;/code&gt;. Sans le savoir, j'avais déjà créé ma première &lt;em&gt;route&lt;/em&gt; index.&lt;/p&gt;

&lt;p&gt;Avec l'arrivée de &lt;strong&gt;React&lt;/strong&gt; et de ses framework, le routing se fait généralement sans configuration de notre part. (sauf si tu utilises React-Router, mais si c'est le cas, tu es au bon endroit !)&lt;/p&gt;

&lt;h2&gt;
  
  
  Explication vidéo
&lt;/h2&gt;

&lt;p&gt;Nous avons fait un guide vidéo pour t'expliquer le routing de Remix avec plus d'exemples. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/TOMMXhgUQfI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Créer sa première route (type 1)
&lt;/h2&gt;

&lt;p&gt;Lorsques tu génères un nouveau projet Remix avec l'un de ses nombreux template, une route par défaut aura déjà été créée. Elle se situe dans ton dossier &lt;code&gt;app/routes&lt;/code&gt; et se nomme &lt;strong&gt;__index.tsx&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello world !&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voici les &lt;strong&gt;2 règles&lt;/strong&gt; à respecter pour créer une route avec la configuration par défaut de Remix :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Créer un fichier au format &lt;strong&gt;.jsx&lt;/strong&gt; ou &lt;strong&gt;.tsx&lt;/strong&gt; dans le dossier &lt;code&gt;app/routes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Le nommer (son nom va définir le chemin pour y accéder dans l'URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dans l'exemple ci-dessous, nous avons ajouté un composant React à l'intérieur de notre fichier. Cette vue sera accessible à la page d'accueil de notre site (par défaut &lt;code&gt;localhost:3000&lt;/code&gt;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Mais il n'est pas obligatoire d'exporter des composants React depuis nos routes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cela dépend de ton besoin. Si tu veux juste créer un endpoint API (une route qui renvoie du JSON, par exemple), tu n'as pas besoin de déclarer un composant React.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Virgile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voici un exemple d'une route Remix, accessible à l'URL &lt;code&gt;localhost:3000/api&lt;/code&gt;. Elle renvoie un objet au format JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Virgile"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Création d'une route dynamique (type 2)
&lt;/h2&gt;

&lt;p&gt;Pour créer une route dynamique, nous devons utiliser un nommage de fichier précis. Il nous suffit d'ajouter le préfixe &lt;strong&gt;$&lt;/strong&gt; à notre nom de fichier.&lt;/p&gt;

&lt;p&gt;Par exemple, si je souhaite afficher le détail de l'utilisateur &lt;em&gt;Virgile&lt;/em&gt;* à l'adresse &lt;code&gt;http://localhost:3000/users/virgile&lt;/code&gt;, je vais implémenter cette logique en deux étapes :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nommer un fichier dans le dossier &lt;code&gt;app/routes&lt;/code&gt;, nommé &lt;code&gt;users.$username.tsx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Exporter par défaut un composant React pour afficher une vue, avec les informations de cet utilisateur&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regardons avec un exemple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useParams&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Salut &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; !&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ici, nous donnons une instruction à Remix. Nous lui demandons de créer une route au chemin &lt;code&gt;users/virgile&lt;/code&gt;. Le point (&lt;code&gt;.&lt;/code&gt;) dans le nommage du fichier nous permet d'ajouter un &lt;em&gt;slash&lt;/em&gt; dans l'URL. Ensuite, le &lt;em&gt;$&lt;/em&gt; nous permet de nommer la clé du paramètre dans l'URL.&lt;/p&gt;

&lt;p&gt;Nous récupérons ensuite ce paramètre dynamique, nommé &lt;em&gt;username&lt;/em&gt; dans notre composant React, côté client à l'aide du hook &lt;strong&gt;useParams()&lt;/strong&gt; que nous importons depuis Remix.&lt;/p&gt;

&lt;p&gt;Ce fichier nous permet donc de créer une route qui va afficher une vue côté client, avec une valeur dynamique en fonction de la valeur que nous entrons dans l'URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IdXmd44R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/image_f689049259.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IdXmd44R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/image_f689049259.png" alt="salut virgile" width="448" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La valeur de notre variable &lt;strong&gt;username&lt;/strong&gt; est déterminée par le paramètre d'URL (soit &lt;em&gt;virgile&lt;/em&gt;, dans cet exemple)&lt;/p&gt;

&lt;h2&gt;
  
  
  Création d'une route imbriquée (type 3)
&lt;/h2&gt;

&lt;p&gt;La route &lt;em&gt;imbriquée&lt;/em&gt; est similaire à la route dynamique. Nous nommons le fichier de la même manière qu'une route dynamique. La seule différence, c'est le contenu de la route &lt;strong&gt;parent&lt;/strong&gt; (dans notre exemple, la route &lt;strong&gt;users.tsx&lt;/strong&gt;) est visible sur la page. C'est très pratique pour afficher par exemple système d'onglets dans notre application. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VBWknehJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/tabs_502db141b3.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VBWknehJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/tabs_502db141b3.jpeg" alt="Exemple de page avec des onglets" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nous pourrions très bien reproduire cet exemple sous forme de route imbriquée. La route parent afficherait les onglets, et le contenu de l'onglet (Subtopic 1) serait affiché dans la route enfant.&lt;/p&gt;

&lt;p&gt;Faisons d'abord une petite refactorisation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Liste des utilisateurs&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Virgile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cédric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous affichons maintenant une liste de deux utilisateurs sur la route &lt;code&gt;localhost:3000/users&lt;/code&gt;, &lt;em&gt;Virgile&lt;/em&gt; et &lt;em&gt;Cédric&lt;/em&gt;.&lt;br&gt;
Nous avons également des liens pour accéder à leur information.&lt;/p&gt;

&lt;p&gt;Spoiler, mais ça ne va pas avoir l'effet désiré. On n'est cependant pas loin du but.&lt;br&gt;
En théorie, il suffirait de changer l'URL à &lt;code&gt;localhost:3000/cedric&lt;/code&gt; pour afficher la vue pour cet utilisateur (c'est la route dynamique que nous avons créée plus haut).&lt;/p&gt;

&lt;p&gt;Malheureusement, après avoir cliqué sur le lien, on se rend compte que la page n'a pas changé 🤔 ??&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G4Cu-Txm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/image_ec2c10077b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G4Cu-Txm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/image_ec2c10077b.png" alt="image.png" width="653" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Et bien, si. Nous avons réussi à donne l'instruction à Remix de créer une route &lt;strong&gt;imbriquée&lt;/strong&gt;. Sauf qu'on a oublié un composant magique nous permettant de placer la position de l'enfant dans le DOM.&lt;/p&gt;

&lt;p&gt;En fait, la page ci-dessus affiche toujours le contenu du parent (les liens de nos utilisateurs), mais elle n'affiche pas la route enfant car on ne lui a pas donné l'instruction. (il n'y a donc aucun bug, rassurez-vous)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Que dois-je faire pour faire apparaître ma route enfant ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le composant &lt;code&gt;&amp;lt;Outlet/&amp;gt;&lt;/code&gt; est la pièce manquante pour bénéficier des routes imbriquées avec Remix. En effet, si nous accédons à la route parent (&lt;code&gt;localhost:3000/users&lt;/code&gt;), Remix affiche la route &lt;code&gt;users.tsx&lt;/code&gt;. Mais si on accède à la route &lt;code&gt;localhost:3000/users/cedric&lt;/code&gt;, Remix va regarder le nom de notre fichier &lt;code&gt;users.$userId.tsx&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Et il va vérifier trois éléments :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Est-ce qu'il y a un paramètre d'URL dynamique (symbolisé par le &lt;code&gt;$&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Est-ce qu'il existe une route parent (dans notre cas, &lt;code&gt;users.tsx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Est-ce que la route parent possède un composant &lt;code&gt;&amp;lt;Outlet/&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rajoutons maintenant le &lt;a href="https://remix.run/docs/en/main/components/outlet"&gt;composant Outlet&lt;/a&gt; et regardons ce qui se passe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Outlet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@remix-run/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Liste des utilisateurs&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Virgile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cédric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Outlet&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o-z1Q9Kl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/image_aff718efe0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o-z1Q9Kl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/image_aff718efe0.png" alt="La route imbriquée s'affiche correctement" width="682" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En résumé, ajouter une route imbriquée est relativement &lt;em&gt;simple&lt;/em&gt;. Il suffit de rajouter le composant &lt;code&gt;&amp;lt;Outlet/&amp;gt;&lt;/code&gt; dans la route parent. Et Remix se charge du reste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Création d'une route layout (type 4)
&lt;/h2&gt;

&lt;p&gt;Reprenons l'exemple de nos onglets pour mieux comprendre l'intérêt d'une route &lt;strong&gt;layout&lt;/strong&gt;. &lt;br&gt;
On va commencer par la définir, en la comparant à une route imbriquée.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Une route layout, c'est une route imbriquée, mais le chemin n'est pas visible dans l'URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Par exemple, si vous souhaitez afficher un composant spécial sur certaines pages, mais pas sur d'autres (ou bien un modal qui apparaît après 5 secondes, c'est vous qui voyez), c'est totalement possible grâce aux routes &lt;em&gt;Layout&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Pour créer une route layout, il suffit de rajouter le préfixe &lt;code&gt;_&lt;/code&gt; (underscore) à la route parent. Son nom va disparaître de l'URL.&lt;br&gt;
Essayons tout de suite avec notre route &lt;code&gt;users.tsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(Le contenu du fichier ne change pas, seulement son nom)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Link, Outlet } from '@remix-run/react';

export default function Users() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Liste des utilisateurs&amp;lt;/h1&amp;gt;
            &amp;lt;ul&amp;gt;
                {['Virgile', 'Cédric'].map((username) =&amp;gt; (
                    &amp;lt;Link to={`/${username}`} key={username}&amp;gt;
                        {username}
                    &amp;lt;/Link&amp;gt;
                ))}
            &amp;lt;/ul&amp;gt;
            &amp;lt;Outlet /&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il faut aussi modifier le fichier &lt;code&gt;users.$username.tsx&lt;/code&gt; pour qu'il soit considéré comme "faisant partie du layout".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useParams } from '@remix-run/react';

export default function Username() {
    const username = useParams().username;
    return &amp;lt;h1&amp;gt;Salut {username} !&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maintenant, notre liste d'utilisateurs ne sera plus visible au chemin &lt;code&gt;localhost:3000/users&lt;/code&gt;. Elle ne sera plus visible du coup, pas même à l'URL &lt;code&gt;localhost:3000&lt;/code&gt; (c'est la route _index.tsx qui s'affiche à la place).&lt;/p&gt;

&lt;p&gt;Le détail de notre utilisateur sera cependant accessible à la route enfant &lt;code&gt;localhost:3000/cedric&lt;/code&gt;, ainsi que le contneu de notre layout (la liste de nos deux utilisateurs)&lt;/p&gt;

&lt;h2&gt;
  
  
  Créer une route ressource (JSON, PDF, CSV ...) (type 5)
&lt;/h2&gt;

&lt;p&gt;Vous vous souvenez de la route basique qu'on a créé en tout début de l'article ? Une &lt;a href="https://remix.run/docs/en/main/guides/resource-routes#creating-resource-routes"&gt;route ressource&lt;/a&gt; est exactement pareil. &lt;/p&gt;

&lt;p&gt;Il suffit de créer un fichier dans le dossier &lt;code&gt;app/routes&lt;/code&gt;, mais au lieu d'exporter un composant React par défaut (notre vue), nous exportons une méthode nommée &lt;code&gt;loader&lt;/code&gt; (pas besoin de l'exporter par défaut)&lt;/p&gt;

&lt;p&gt;Essayons avec un exemple. Je souhaite afficher une image aléatoire à l'URL &lt;code&gt;localhost:3000/image&lt;/code&gt;. Je vais donc récupérer mon image depuis le site &lt;a href="https://picsum.photos"&gt;picsum.photos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Il me suffit donc de créer un fichier nommé &lt;code&gt;image.tsx&lt;/code&gt; (dans le dossier &lt;code&gt;app/routes&lt;/code&gt;), puis d'exporter une &lt;em&gt;fonction&lt;/em&gt; nommée &lt;code&gt;loader&lt;/code&gt;&lt;br&gt;
Essayons voir.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const loader = async () =&amp;gt; {
    const response = await fetch('https://picsum.photos/200/300');
    const blob = await response.blob();
    return new Response(blob, {
        headers: {
            'Content-Type': 'image/jpeg',
        },
    });
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VkYYGCF_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/resource_route_203bd1f2a2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VkYYGCF_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/resource_route_203bd1f2a2.gif" alt="resource-route.gif" width="419" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Je n'ai jamais créé une route API aussi facilement. &lt;/p&gt;

&lt;h2&gt;
  
  
  Créer une route 404 (type 6)
&lt;/h2&gt;

&lt;p&gt;Nous allons maintenant créer notre dernier type de route, la fameuse page &lt;em&gt;Erreur 404&lt;/em&gt;. &lt;br&gt;
Si vous lisez l'article depuis le début, vous comprendrez vite comment l'implémenter.&lt;br&gt;
Indice : C'est comme une route dynamique, mais le paramètre d'URL n'est pas nommé.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Il faut nommer le fichier &lt;code&gt;$.tsx&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pour afficher une vue à n'importe quelle page que Remix n'identifie pas comme étant un chemin valide, il faut également exporter un composant React par défaut. Passons à l'implémentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useLocation } from '@remix-run/react';

export default function FourOhFour() {
    const location = useLocation();
    return (
        &amp;lt;h1 style={{ fontSize: 18 }}&amp;gt;
            Oops, on a pas trouvé la route{' '}
            &amp;lt;span style={{ color: 'GrayText' }}&amp;gt;{location.pathname}&amp;lt;/span&amp;gt; !
        &amp;lt;/h1&amp;gt;
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essayons un chemin pour voir. Au hasard, &lt;code&gt;localhost:3000/youtube/abonnez-vous&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3goS1WlX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/404_route_951a872cfc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3goS1WlX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://algomax-public.s3.eu-west-3.amazonaws.com/404_route_951a872cfc.gif" alt="Route 404" width="527" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Petites précisions :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remix priorise les routes nommées. Si vous tapez &lt;code&gt;localhost:3000/image&lt;/code&gt; dans l'URL, Remix va d'abord regarder les routes nommées, pas dynamiques (un match exact) avec le nom. C'est le cas pour nous avec le fichier &lt;code&gt;image.tsx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ensuite s'il ne trouve pas, il va chercher une route dynamique (avec le préfixe &lt;code&gt;$&lt;/code&gt;). C'est le cas avec le fichier &lt;code&gt;_users.$username.tsx&lt;/code&gt;, qui va matcher n'importe qu'elle URL à la place de l'étoile &lt;code&gt;localhost:3000/*&lt;/code&gt;, à l'exception des routes nommées (exact match), et des routes imbriquées (plusieurs &lt;code&gt;/&lt;/code&gt; (slash) dans l'URL)&lt;/li&gt;
&lt;li&gt;Nous essayons d'accéder à la route &lt;code&gt;localhost:3000/youtube/abonnez-vous&lt;/code&gt;, qui n'est donc pas un paramètre d'URL car il y a un &lt;code&gt;/&lt;/code&gt;. On renvoie donc sur une page 404.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Nous venons de voir tous les types de page nécessaires pour vous permettre de créer des belles &lt;a href="https://dev.to/creation-applications-web-sur-mesure"&gt;applications web sur mesure&lt;/a&gt;. Si vous voulez aller plus loin et configurer vous-même vos règles de routing (ou changer les préfixes, tels que &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt; et &lt;code&gt;_&lt;/code&gt;, c'est possible. La librairie &lt;a href="https://github.com/kiliman/remix-flat-routes"&gt;remix-flat-routes&lt;/a&gt; vous le permet, et je l'utilise dans tous mes projets !&lt;/p&gt;

</description>
      <category>remix</category>
      <category>react</category>
      <category>webdev</category>
      <category>remixjs</category>
    </item>
  </channel>
</rss>
