Express 09.2 - ✅ Upload de fichier (front)
Dans cette quête tu vas voir comment envoyer des fichiers via les formulaires HTML.
Pour que tout se passe au mieux, nous te conseillons d'avoir réalisé au préalable :
Objectifs
- Comprendre le mécanisme derrière l'upload de fichier
- Comprendre le lien entre le formulaire HTML et le serveur
Introduction
Dans les quêtes précédentes, tu as appris à gérer des formulaires avec React. Des textes, des boutons radio, des checkbox, etc.
Mais parfois (spoiler: souvent !) tu auras besoin d'uploader des fichiers de ton frontend vers ton backend (peu importe comment il est construit, le principe restera la même). Que ce soit pour envoyer une pièce jointe dans un mail, une image pour un avatar, ou même les signatures sur Odyssey, on téléverse (oui, c'est le nom français !) énormément de fichiers... Mais il se passe quoi, en fait, quand on upload un fichier ?
Pour la suite, imaginons que Franklin (frontend, abrégé "F.") souhaite envoyer un fichier à Balthazar (backend, "B."). Pour ce faire, il n'a qu'un unique moyen de communiquer avec lui: un formulaire.
1. Le concept
Avant même de parler de code, voyons rapidement ce qui se passe pour F. et B. lors d'un upload de fichier.
- F. remplit son formulaire comme il en a l'habitude. Dans un des champs, au lieu de mettre une chaîne de caractères son navigateur lui propose de choisir un fichier de son ordinateur
- F. soumet le formulaire. Le fichier est transformé dans un format adapté au transport sur le réseau, puis les données (textes + fichier transformé) sont envoyées
vers l'infini et au delàsur le web, vers le serveur sur lequel le backend est hébergé et via la route backend qui convient - B. reçoit le formulaire comme il recevrait n'importe quel appel d'API, le code correspondant à cette route doit être exécuté. Mais il reçoit en plus "en marge" du formulaire un "tas de données" non exploitables en l'état
- B. reconstitue le fichier original à partir du tas de données
- B. déplace le fichier là où ça l'arrange (sinon, c'est un peu comme laisser son courrier s'entasser dans la boite aux lettres)
- B. est poli, il informe F. que tout s'est bien passé
- F. reçoit la validation, son formulaire (et son fichier) son bien arrivés à destination ! Il peut afficher un message au visiteur
Comme tu le vois, il y a pas mal de choses à faire ! Afin de bien séparer les choses, dans cette quête on ne s'occupera que du boulot du F.
2. Le code
On va encore une fois séparer les différentes parties du code à écrire dans notre frontend:
- Ecrire un composant de formulaire, capable de gérer les modifications de ses champs et de réagir à la validation
- Dans la gestion des changements, prendre en compte les spécificités des champs
input type="file" - Dans la gestion de la soumission
- transformer notre fichier en "tas de données"
- regrouper toutes les valeurs de nos champs dans une variable prête-à-l'emploi pour fetch/axios
- envoyer à notre backend la variable ci-dessus
N'hésite pas à essayer de réaliser ces différentes parties par toi-même avant de copier la solution !
2.1 Le formulaire proprement dit
Commençons par la base: un formulaire en HTML:
<form method="POST" enctype="multipart/form-data" action="http://your.backend/api/avatar">
<input type="file" name="avatar">
<button type="submit">Envoyer</button>
</form>
... Tiens, cette balise form a pas la même tête que d'habitude ! En effet, en HTML (j'insiste bien, on ne parle pas encore de React !) un formulaire a besoin de ces attributs pour fonctionner :
methodcorrespond aux verbes HTTP que tu connais. On peut utiliser "GET" ou "POST" en HTMLenctypepermet de signaler au destinataire du formulaire qu'il y a un tas de données en marge du formulaire ("plusieurs parties: des champs de formulaire"). Sans cet attribut et sa valeur, pas moyen d'uploader quoi que ce soit !actionpermet à HTML de savoir où envoyer les données lorsque le visiteur soumet le formulaire
C'est un peu verbeux tout ça, et comme on bosse avec React on pourra se passer des attributs method (c'est fetch/axios qui choisira) et action (le listener de soumission saura où envoyer tout ça) ! On devra par contre garder l'enctype et ajouter un listener de soumission (onSubmit)
Ce qui nous donne, une fois "Reactifié":
<form enctype="multipart/form-data" onSubmit={hSubmit}>
<input type="file" name="monfichier" ref={inputRef} />
<button type="submit">Envoyer</button>
</form>
Cela implique de déclarer inputRef dans ton composant. Regarde la documentation de useRef si l'outil est inhabituel pour toi.
2.2 Le listener d'évènements
Puisqu'on a décidé que le composant allait appeler hSubmit, il va nous falloir le créer!
hSubmit(evt){
evt.preventDefault()
axios.post("http://your.backend/api/avatar", inputRef.current.value)
}
Rappel: avec la syntaxe proposée ci-dessus, le fonction recevra automatiquement un argument contenant l'évènement déclenché, qu'on va stocker dans le paramètre evt
Et, si tu testes ce code (ou que tu l'as copié sans lire la quête... Mais tu ne ferais pas ça, n'est-ce-pas ?), tu vas voir que plus rien ne fonctionne ! En effet, il y a plein de petites erreurs ici:
L'expression inputRef.current.value renvoie undefined. Pour différencier les champs textuels des champs de données, les informations sur les fichiers sont stockés dans des champs différents. Pour les champs de données, on retrouve notre valeurs dans inputRef.current.files.
Mais il y a encore un "hic" ! Un champ de formulaire peut techniquement recevoir plusieurs fichiers... Du coup, inputRef.current.files est un tableau ! Mais comme ici tu ne veux uploader qu'un fichier on ne veut pas s'encombrer de ce tableau, on veut juste le premier (et seul) fichier choisi. D'où le [0] que tu trouveras dans la solution ci-dessous.
Enfin, tu transmets pour le moment à ton backend le nom du fichier au lieu de son contenu. Il manque la transformation en "tas de données" avant l'envoi. Pour cela, on va créer une instance de classe JS nommée FormData et y ajouter ("append" en Anglais) notre fichier.
Une fois tout ça corrigé, voilà une version fonctionnelle de nos listeners:
hSubmit(evt){
evt.preventDefault()
const formData = new FormData();
formData.append("avatar", inputRef.current.files[0]);
axios.post("http://your.backend/api/avatar", formData)
}
Une fois tout ça fait, ton fichier est prêt à partir vers son backend !
`
==$==
Solution
Voilà un composant React permettant de gérer un formulaire. (Encore une fois, il lui faut un backend pour fonctionner)
import { useRef, useState } from "react"
export default function MyForm() {
const inputRef = useRef();
const [msg, setMsg] = useState("Aucun upload effectué")
const hSubmit = (evt)=>{
evt.preventDefault()
const formData = new FormData();
formData.append("avatar", inputRef.current.files[0]);
axios.post("http://your.backend/api/avatar", formData)
.then(()=>{
setMsg("Upload réussi !")
})
.catch(()=>{
setMsg("Upload échoué !")
});
}
return (
<>
<form onSubmit={hSubmit}>
<input type="file" ref={inputRef} />
<button type="submit">Envoyer!</button>
</form>
<p>{msg}</p>
</>
);
}
Ressources partagées sur cette quête
- Pour ceux qui préfèrent fetch(), n’oubliez pas de renseigner le second paramètre pour les options (method et body, en l’occurrence)https://developer.mozilla.org/fr/docs/Web/API/Fetch_API/Using_Fetch#fournir_des_options_à_la_requête
- Livecoding d'un formateur de la wildhttps://reactjsexample.com/learn-how-to-upload-from-react-wiht-multer-and-express/