Le composant natif <form>
du navigateur vous permet de créer des champs interactifs pour envoyer des informations.
<form action={search}>
<input name="query" />
<button type="submit">Rechercher</button>
</form>
- Référence
- Utilisation
- Gérer l’envoi de formulaire côté client
- Gérer l’envoi de formulaire dans une Action Serveur
- Afficher un état d’attente pendant l’envoi du formulaire
- Mettre à jour les données de formulaire de façon optimiste
- Gérer les erreurs d’envoi du formulaire
- Afficher une erreur d’envoi de formulaire sans JavaScript
- Gérer plusieurs types d’envois
Référence
<form>
Pour créer des formulaires interactifs, utilisez le composant natif <form>
du navigateur.
<form action={search}>
<input name="query" />
<button type="submit">Rechercher</button>
</form>
Voir d’autres exemples plus bas.
Props
<form>
prend en charge toutes les props communes aux éléments.
action
: une URL ou une fonction. Lorsqu’une URL est passée à action
, le formulaire se comporte comme un formulaire HTML classique. Mais si une fonction est passée à action
, la fonction traitera l’envoi du formulaire. La fonction passée à action
peut être asynchrone et sera appelée avec un unique argument contenant les données envoyées par le formulaire. La prop action
peut céder la priorité à la prop formAction
d’un composant <button>
, <input type="submit">
, ou <input type="image">
.
Limitations
- Lorsqu’une fonction est passée à
action
ouformAction
, la méthode HTTP sera POST indépendamment de la valeur de la propmethod
.
Utilisation
Gérer l’envoi de formulaire côté client
Passez une fonction à la prop action
du formulaire pour exécuter cette fonction lors de l’envoi du formulaire. Les formData
lui seront passées en argument, afin que vous puissiez accéder aux données envoyées par le formulaire. C’est là une différence avec l’attribut HTML action
, qui n’accepte que des URL.
export default function Search() { function search(formData) { const query = formData.get("query"); alert(`Vous avez recherché « ${query} »`); } return ( <form action={search}> <input name="query" /> <button type="submit">Rechercher</button> </form> ); }
Gérer l’envoi de formulaire dans une Action Serveur
Affichez un <form>
avec un champ de saisie et un bouton d’envoi, puis passez-lui une Action Serveur (une fonction dotée de la directive 'use server'
) via sa prop action
pour exécuter cette fonction quand le formulaire sera envoyé.
Passer une Action Serveur à <form action>
permet aux utilisateurs d’envoyer le formulaire même sans JavaScript activé, ou avant que le code JavaScript ne soit chargé et exécuté. C’est bien pratique pour les utilisateurs ne disposant que d’une connexion ou d’un appareil lents, ou qui ont JavaScript désactivé. C’est d’ailleurs un comportement similaire à celui des formulaires dont la prop action
contient une URL.
Vous pouvez utiliser des champs cachés pour fournir des données à l’action du <form>
. L’Action Serveur récupèrera ces données de champs cachés au moyen d’une instance de FormData
.
import { updateCart } from './lib.js';
function AddToCart({productId}) {
async function addToCart(formData) {
'use server'
const productId = formData.get('productId')
await updateCart(productId)
}
return (
<form action={addToCart}>
<input type="hidden" name="productId" value={productId} />
<button type="submit">Ajouter au panier</button>
</form>
);
}
Plutôt que de fournir les données à l’action du <form>
au moyen de champs cachés, vous pouvez recourir à la méthode bind
pour pré-remplir ses arguments. Dans l’exemple qui suit, on pré-remplit un argument (productId
) pour la fonction, en plus des formData
qui lui sont passées par défaut.
import { updateCart } from './lib.js';
function AddToCart({productId}) {
async function addToCart(productId, formData) {
"use server";
await updateCart(productId)
}
const addProductToCart = addToCart.bind(null, productId);
return (
<form action={addProductToCart}>
<button type="submit">Ajouter au panier</button>
</form>
);
}
Lorsqu’un <form>
fait son rendu au sein d’un Composant Serveur, et qu’en prime une Action Serveur est passée à la prop action
du <form>
, le formulaire bénéficie d’une amélioration progressive.
Afficher un état d’attente pendant l’envoi du formulaire
Pour afficher un état d’attente dans un formulaire pendant son envoi, vous pouvez utiliser le Hook useFormStatus
dans un composant affiché au sein d’un <form>
, et lire la propriété pending
qu’il renvoie.
Nous utilisons ci-dessous la propriété pending
pour indiquer que le formulaire est en cours d’envoi.
import { useFormStatus } from "react-dom"; import { submitForm } from "./actions.js"; function Submit() { const { pending } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? "Envoi en cours..." : "Envoyer"} </button> ); } function Form({ action }) { return ( <form action={action}> <Submit /> </form> ); } export default function App() { return <Form action={submitForm} />; }
Pour en apprendre davantage, consultez la documentation de référence du Hook useFormStatus
.
Mettre à jour les données de formulaire de façon optimiste
Le Hook useOptimistic
fournit un moyen de mettre à jour l’UI de façon optimiste le temps qu’une opération d’arrière-plan, telle qu’une requête réseau, aboutisse. Dans le contexte des formulaires, cette technique permet d’améliorer la fluidité perçue de l’appli. Lorsqu’un utilisateur envoie un formulaire, plutôt que d’attendre la réponse du serveur avant de refléter les changements, l’interface peut être mise à jour immédiatement avec le résultat prévu.
Lorsqu’un utilisateur saisit par exemple un message dans un formulaire puis clique sur le bouton « Envoyer », le Hook useOptimistic
permet à ce message d’apparaître immédiatement dans la liste avec une étiquette « Envoi… », avant même que le message ne soit réellement envoyé au serveur. Cette approche « optimiste » donne une impression de vitesse et de réactivité. Le formulaire tente ensuite de réellement envoyer le message en arrière-plan. Une fois que le serveur en a confirmé réception, l’étiquette « Envoi… » est retirée.
import { useOptimistic, useState, useRef } from "react"; import { deliverMessage } from "./actions.js"; function Thread({ messages, sendMessage }) { const formRef = useRef(); async function formAction(formData) { addOptimisticMessage(formData.get("message")); formRef.current.reset(); await sendMessage(formData); } const [optimisticMessages, addOptimisticMessage] = useOptimistic( messages, (state, newMessage) => [ ...state, { text: newMessage, sending: true } ] ); return ( <> {optimisticMessages.map((message, index) => ( <div key={index}> {message.text} {!!message.sending && <small> (Envoi...)</small>} </div> ))} <form action={formAction} ref={formRef}> <input type="text" name="message" placeholder="(exemple : Salut !)" /> <button type="submit">Envoyer</button> </form> </> ); } export default function App() { const [messages, setMessages] = useState([ { text: "Coucou toi !", sending: false, key: 1 } ]); async function sendMessage(formData) { const sentMessage = await deliverMessage(formData.get("message")); setMessages([...messages, { text: sentMessage }]); } return <Thread messages={messages} sendMessage={sendMessage} />; }
Pour en apprendre davantage, consultez la documentation de référence du Hook useOptimistic
.
Gérer les erreurs d’envoi du formulaire
Il peut arriver que la fonction appelée par la prop action
de <form>
lève une erreur. Vous pouvez traiter ces erreurs en enrobant <form>
dans un périmètre d’erreur. Si la fonction appelée par la prop action
de <form>
lève une erreur, le contenu de secours du périmètre d’erreur sera affiché.
import { ErrorBoundary } from "react-error-boundary"; export default function Search() { function search() { // Pour les besoins de la démonstration uniquement if(comment == null){ throw Error('Example error') } } return ( <ErrorBoundary fallback={<p>Une erreur est survenue lors de l’envoi du formulaire</p>} > <form action={search}> <input name="query" /> <button type="submit">Rechercher</button> </form> </ErrorBoundary> ); }
Afficher une erreur d’envoi de formulaire sans JavaScript
Afin d’afficher un message d’erreur d’envoi de formulaire avant même que le bundle JavaScript soit chargé et exécuté (à des fins d’amélioration progressive), plusieurs choses sont nécessaires :
- le
<form>
doit figurer dans un Composant Serveur - la fonction passée à la prop
action
du<form>
doit être une Action Serveur - le Hook
useFormState
doit être utilisé pour produire le message d’erreur
useFormState
accepte deux arguments : une Action Serveur et un état initial. useFormState
renvoie deux valeurs : une variable d’état et une action. L’action ainsi renvoyée par useFormState
doit être passée à la prop action
du formulaire. La variable d’état renvoyée par useFormState
peut être utilisée pour afficher le message d’erreur. La valeur renvoyée par l’Action Serveur passée à useFormState
sera utilisée pour mettre à jour la variable d’état.
import { useFormState } from "react-dom"; import { signUpNewUser } from "./api"; export default function Page() { async function signup(prevState, formData) { "use server"; const email = formData.get("email"); try { await signUpNewUser(email); alert(`Inscription de « ${email} » confirmée`); } catch (err) { return err.toString(); } } const [message, formAction] = useFormState(signup, null); return ( <> <h1>Inscris-toi à ma newsletter</h1> <p>L’utilisation d’un e-mail déjà inscrit produira une erreur</p> <form action={formAction} id="signup-form"> <label htmlFor="email">E-mail : </label> <input name="email" id="email" placeholder="(exemple : react@example.com)" /> <button>Inscription</button> {!!message && <p>{message}</p>} </form> </> ); }
Apprenez-en avantage sur la mise à jour de l’état depuis une action de formulaire dans la documentation de référence du Hook useFormState
.
Gérer plusieurs types d’envois
Il est possible de concevoir un formulaire pour qu’il gère plusieurs actions d’envoi selon le bouton pressé par l’utilisateur. Chaque bouton au sein du formulaire peut être associé à une action ou un comportement distincts au moyen de sa prop formAction
.
Lorsque l’utilisateur active un bouton précis, le formulaire est envoyé, et l’action correspondante (définie par les attributs et l’action du bouton) est exécutée. Un formulaire pourrait par exemple publier un article par défaut, mais disposer par ailleurs d’un bouton distinct avec sa propre formAction
pour simplement le stocker comme brouillon.
export default function Search() { function publish(formData) { const content = formData.get("content"); const button = formData.get("button"); alert(`« ${content} » vient d’être publié avec le bouton « ${button} »`); } function save(formData) { const content = formData.get("content"); alert(`Votre brouillon de « ${content} » est sauvegardé !`); } return ( <form action={publish}> <textarea name="content" rows={4} cols={40} /> <br /> <button type="submit" name="button" value="submit">Publier</button> <button formAction={save}>Enregistrer comme brouillon</button> </form> ); }