Ce dojo est l'occasion parfaite de revoir les bases de React, en se concentrant
sur la gestion du state
et la communication avec une API distante.
L'objectif est de compléter une application gérant une liste de tâches :
En l'état, elle permet déjà, en local :
- D'ajouter une tâche à la liste
- De mettre à jour le status de complétion d'une tâche en particulier
- De supprimer une tâche de la liste
Maintenant, on aimerait bien pouvoir sauvegarder les tâches que l'on renseigne sur un serveur distant, afin de les persister sur un support pour les retrouver lorsque l'on recharge l'application !
Par chance, un serveur Node a déjà été développé (cf: dossier server
pour les curieux).
Il permet déjà de gérer et persister temporairement la liste de tâches. Il n'y aura donc plus
qu'à "câbler" le client React avec ce dernier !
Pour cela, le serveur expose une API REST (basée sur le protocole HTTP et le format JSON) qu'il faudra requêter en lecture et en écriture, à l'aide de fetch ou axios, pour réaliser les opérations CRUD sur ces tâches.
Une fois le serveur démarré, il est possible d'accéder à la documentation interactive de l'API à l'adresse http://localhost:5000/api-docs/.
Installer et démarrer le serveur :
cd server && npm i && npm start
Dans un terminal séparé, démarrer le client
npm i && npm start
Ouvrir ce dernier dans un navigateur et explorer un peu l'application d'un point de vue fonctionnel.
Rendez-vous dans App.js
, observer un peu le code existant, on pourra en
tirer parti lors du développement des nouvelles fonctionnalités.
Par soucis de simplicité, toute l'application tient
dans le composant App et il n'y aura presque pas à intervenir au niveau
de la méthode render
, puisque la partie "affichage" est déjà
en grande partie développée.
Allez voir la documentation de l'API et intéragissez avec cette dernière pour vous familiariser un peu avec (boutons "Try it out" puis "Execute" sur chaque route).
Si les fonctionnalités sont plus ou moins indépendantes les unes des autres, il est tout de même recommandé de les faire dans l'odre qui suit (au moins les critères "MUST"), afin de faciliter certains tests.
Une fois le serveur démarré, écrire la méthode fetchTasks
.
Action(s) : On se rend sur l'application
Critères :
- (MUST) La liste des tâches doit être chargée quand on arrive sur l'application.
- (MUST) Quand la liste est chargée, on doit voir 3 tâches (si aucune tâche n'a été ajoutée).
- (SHOULD) Quand la liste charge, un indicateur est affiché.
- (COULD) Si le serveur n'est pas démarré (simulation d'une communication impossible avec l'API), un message "Can't load tasks form the server" est affiché après l'échec de la requête.
- (COULD) Au survol d'une tâche dans la liste, on voit "Created <x> <minutes|seconds|hours> ago"
- Il faudra requêter l'URL http://localhost:5000/tasks
pour charger la liste des tâches.
Il faudra également gérer les données suivantes dans le
state
:loadingTasks
: booléen indiquant si la liste est en cours de chargement. C'est ce qui déclenchera l'affichage d'un indicateur de chargement au niveau de la vue.taskList
: Un tableau contenant toutes les tâches sous forme d'objets
- Pour la gestion d'erreur, voir la méthode
showErrorMessage
dansApp
- Pour le dernier critère, rechercher
fromNow
dansApp
, puis recherchertask relative creation time here
;)
Réécrire la méthode createTask
pour synchroniser la tâche créée sur le serveur.
Action(s) : On tape un nom pour la nouvelle tâche, on appuie sur le bouton "+" (ou on fait Entrée).
Critères :
- (MUST) Une fois qu'elle a été créee sur le serveur, la nouvelle tâche doit être affichée dans la liste sur le client.
- (MUST) Une fois qu'elle a été créee sur le serveur, on peut recharger la page et constater que la nouvelle tâche est toujours présente.
- (SHOULD) Quand les données sont en train d'être traitées sur le serveur, on voit "Submitting task <name>..." s'afficher dans le champ "nom" du formulaire de la nouvelle tâche.
- (SHOULD) Lorsque la tâche a été créée sur le serveur, un message de succès "Task <name> successfully created on the server !" est affiché
- (COULD) Si le serveur est n'est pas démarré et qu'on essaie de créer une tâche, un message d'erreur "Can't create task "<name>" on server" est affiché
- (COULD) Si on entre un nom de tâche qui existe déjà, le message d'erreur renvoyé par le serveur est affiché à l'utilisateur.
- Il faudra requêter l'URL http://localhost:5000/tasks, cette fois avec le verbe HTTP POST. Les attributs de la nouvelle tâche devront être passés au serveur dans le corps de la requête, comme sur l'image suivante :
- Si nécéssaire, se renseigner sur comment passer des données dans le corps de la requête avec axios ou fetch en lisant la doc ;)
- La tâche devra être ajoutée dans le tableau de tâches
taskList
présent dansstate
après que l'API ait répondu avec l'objet "tâche" créé sur le serveur. - Pour le critère 3, il faudra dans
this.state.newTask
gérer une clé_submitting
dont la valeur sera un booléen indiquant à la vue que la création de la tâche sur le serveur est en cours de traitement. - Pour les derniers critères, voir les méthodes
showSuccessMessage
etshowErrorMessage
deApp
. - Pour le critère 6, si l'API renvoie un message d'erreur, on pourra récupérer
ce dernier dans
error.response.data.errorMessage
(sierror
est la variable représentant l'erreur au niveau du bloccatch
).
Réécrire la méthode updateTask
pour synchroniser la tâche le serveur suite au cochage/décochage de la checkbox.
Action(s) : On coche/décoche une checkbox sur une tâche
Critères :
- (MUST) Une fois qu'elle a été mise à jour sur le serveur, le status de complétion de la tâche est mis à jour sur le client.
- (MUST) Une fois qu'elle a été mise à jour sur le serveur, on peut recharger la page et constater qu'elle est restée dans le dernier état de complétion renseigné.
- (SHOULD) Quand les données sont en train d'être traitées sur le serveur, on voit "Updating task <name>..." s'afficher dans à la place du nom de la tâche.
- (SHOULD) Lorsque la tâche a bien été modifiée sur le serveur, un message de succès "Task <name> successfully updated on the server !" est affiché.
- (COULD) Si le serveur est n'est pas démarré et qu'on essaie de modifier une tâche, un message d'erreur "Can't update this task on server" est affiché.
- (COULD) Si on tente de modifier une tâche qui n'existe plus sur le serveur, le message d'erreur renvoyé par le serveur est affiché à l'utilisateur. Pour tester : creer une nouvelle tâche, redémarrer le serveur et essayer de modifier cette dernière tâche (sans avoir rechargé l'application).
- Il faudra requêter l'URL
http://localhost:5000/tasks/:id
, avec le verbe HTTP PATCH, où il faudra remplacer:id
par l'identifiant de la tâche à modifier (id
dans les objets "tâche"). Les nouveaux attributs de la tâche (ceux qui ont possiblement changé, icidone
) devront être passés au serveur dans le corps de la requête. Si nécéssaire, se renseigner sur comment spécifier le verbe PATCH et passer des données dans le corps de la requête avec axios ou fetch en lisant la doc ;) - Pour le critère 3, il faudra gérer une clé
_updating
dont la valeur sera un booléen indiquant à l'interface si la tâche en question est en cours de synchronisation. Cette clé devra être présente dans l'objet "tâche" en cours de mise à jour, cet objet est contenu dans le tableautaskList
dustate
. - Pour le critère 6, si l'API renvoie un message d'erreur, on pourra récupérer
ce dernier dans
error.response.data.errorMessage
(sierror
est la variable représentant l'erreur au niveau du bloccatch
).
Réécrire la méthode updateTask
pour supprimer la tâche sur le serveur.
Action(s) : On clique sur le bouton "mettre à la poubelle" au niveau d'une tâche
Critères :
- (MUST) Une fois qu'elle a supprimée sur le serveur, elle est retirée de la liste sur le client.
- (MUST) Une fois la tâche supprimée, on peut recharger la page et constater que cette dernière n'est pas réapparue et a donc bien été supprimée sur le serveur.
- (SHOULD) Quand la suppression est en cours sur le serveur, on voit "Deleting task <name>..." s'afficher dans à la place du nom de la tâche.
- (SHOULD) Lorsque la tâche a été supprimée sur le serveur, un message de succès "Task <name> successfully deleted on the server !" est affiché
- (COULD) Si le serveur est n'est pas démarré et qu'on essaie de supprimer une tâche, un message d'erreur "Can't delete this task on server" est affiché
- (COULD) Si on tente de supprimer une tâche qui n'existe plus sur le serveur, le message d'erreur renvoyé par le serveur est affiché à l'utilisateur. Pour tester facilement : creer une nouvelle tâche, redémarrer le serveur et essayer de supprimer cette dernière (sans avoir rechargé l'application).
- Il faudra requêter l'URL
http://localhost:5000/tasks/:id
, avec le verbe HTTP DELETE, où il faudra remplacer:id
par l'identifiant de la tâche à supprimer. Si nécéssaire, se renseigner sur comment spécifier le verbe DELETE avec axios ou fetch en lisant la doc ;) - Pour le critère 3, il faudra gérer une clé
_deleting
dont la valeur sera un booléen indiquant à l'interface si la tâche en question est en cours de synchronisation. Cette clé devra être présente dans l'objet "tâche" en cours de suppression, cet objet est contenu dans le tableautaskList
dustate
. - Pour le critère 6, si l'API n'a pas trouvé la tâche
à supprimer,
error.response.status
vaudra404
(sierror
est la variable représentant l'erreur au niveau du bloccatch
).
Les opérations CRUD font partie du quotidien des développeurs, car c'est selon ce mode opératoire que sont développées les applications qui gèrent des "ressources" (des tâches, des clients, des commandes, des commentaires, des utilisateurs, ..., bref: des données métier que l'on souhaite bien souvent persister afin de les manipuler par la suite). Autant dire que cette notion de "CRUD" est omniprésente dans l'immense majorité des applications web !
Comme cette application manipule des données relativement simples, il est tout à fait possible (et même souhaitable) de la reprendre et de la completer tout au long de la formation pour revoir la base (qui sera très vite indispensable) ou encore pour expérimenter de nouvelles choses :)
Voici quelques suggestions pour poursuivre ce dojo, classées par ordre de difficulté/temps d'apprentissage.
- Un bouton "reload" permettant de recharger la liste des tâches depuis le serveur
- Un bouton "afficher uniquement les tâches à faire" / "afficher toutes les tâches"
- Un bouton "Tout cocher" qui ira mettre
done
àtrue
pour toutes les tâches sur le serveur et le client - Un bouton "Tout décocher" qui ira mettre
done
àfalse
pour toutes les tâches sur le serveur et le client - Un filtre par nom de tâche
- Permettre d'entrer plusieurs tâche d'un coup en spérant leurs noms par des vigules (ou un autre séparateur)
- Réusiner le code (mieux compartimenter les composants, améliorer la partie UI/UX, utiliser les hooks, ...)
- Utiliser l'API Context pour permettre le choix entre plusieurs thèmes ("mode dark", ...)
- (+) Au double-clique sur une tâche, permettre d'éditer le nom dans un formulaire et répercuter les modifications sur le serveur quand on a arreté d'écrire depuis x secondes par exemple, ou/et quand on appuie sur Entrée.
- (+) Utiliser i18n pour internationaliser l'interface.
- (++) Actuellement, on ajoute les propriétés
_submitting
,_updating
ou_deleting
sur des objets métiers (les tâches) afin d'avoir facilement des informations sur leurs états individuels de synchronisation avec l'API. Comme c'est une petite application ce n'est pas vraiment dérangeant, mais rajouter des proriétés de cette manière n'est en règle général pas conseillé car on risque d'entrer en collision avec le nom de propriétés des objets qui provienent d'une source externe. Une solution serait d'utiliser les symboles pour éviter de polluer le namespace de nos tâches. - (++) Implémenter des mises à jour "optimistes" sur l'interface.
- (+++) Implémenter une gestion du state centralisée avec Redux.
- (+++) Implémenter les fonctionnalités d'une PWA (mode "offline", ...).
- (+++) Aller voir comment est programmée l'API, tenter de persister les infos dans une base de donnée MySQL (ou autre).
- (+++) Complétez serveur et client pour implémenter une pagination des tâches.
- (+++) Completez serveur et client pour gérer des "chronomètres" (démarrer, mettre en pause, reprendre, remettre à zero) sur les tâches.
- (+++) Completez serveur et client pour gérer une
position
dans la liste pour les tâches. Au niveau de l'interface, on pourrait les réordonner avec du "Drag n' Drop" ! - (++++) Implémenter une communication bi-directionnelle entre client et serveur, pour faire une application en "temps réel" (si je lance un autre client et que le premier ajoute une tâche, je la vois automatiquement apparaître sans aucune action de ma part). On pourrait par exemple utiliser socket.io et l'intégrer au serveur Node existant, ou carrément repartir sur un autre back-end en utilisant Firebase par exemple.