La construction d'un projet moderne nécessite de diviser la logique en code frontal et principal. La raison de cette décision est de promouvoir la réutilisation du code. Par exemple, il se peut que nous devions créer une application mobile native qui accède à l'API principale. Ou nous développons peut-être un module qui fera partie d'une grande plate-forme modulaire.
La façon populaire de construire une API côté serveur consiste à utiliser Node.js avec une bibliothèque comme Express ou Restify. Ces bibliothèques facilitent la création d'itinéraires RESTful. Le problème avec ces bibliothèques est que nous nous retrouverons à écrire une tonne de code répétitif . Nous aurons également besoin d'écrire du code pour l'autorisation et d'autres logiques de middleware.
Pour échapper à ce dilemme, nous pouvons utiliser un cadre comme Feathers pour nous aider à générer une API en seulement quelques commandes. [19659004] Ce qui rend Feathers incroyable, c'est sa simplicité. L'ensemble du cadre est modulaire et nous avons seulement besoin d'installer les fonctionnalités dont nous avons besoin. Feathers lui-même est une enveloppe mince construite au-dessus d'Express, où ils ont ajouté de nouvelles fonctionnalités – services et crochets . Feathers nous permet également d'envoyer et de recevoir des données sans effort via WebSockets.
Prérequis
Pour suivre ce didacticiel, vous aurez besoin des éléments suivants installés sur votre machine:
- Node.js v12 + et une version à jour de npm. Consultez ce didacticiel si vous avez besoin d'aide pour la configuration.
- MongoDB v4.2 +. Consultez ce didacticiel si vous avez besoin d'aide pour la configuration.
- Yarn gestionnaire de paquets – installé à l'aide de
npm i -g yarn
.
Cela vous aidera également si vous suis familier avec les sujets suivants:
- comment écrire du JavaScript moderne
- contrôle de flux en JavaScript moderne (par exemple
async ... attend
) - les bases de React
- les bases de API REST
Notez également que vous pouvez trouver le code de projet terminé sur GitHub .
Échafaudez l'application
Nous ' re va construire une application de gestionnaire de contacts CRUD en utilisant Node.js, React, Feathers et MongoDB.
Dans ce tutoriel, je vais vous montrer comment construire l'application de bas en haut. Nous allons lancer notre projet en utilisant l'outil populaire create-react-app .
Vous pouvez l'installer comme suit:
npm install -g create-react-app
Créez ensuite un nouveau projet:
# échafaudez un nouveau projet React
créer-réagir-application réagir-contacter-gestionnaire
cd react-contact-manager
# supprimer les fichiers inutiles
rm src / logo.svg src / App.css src / serviceWorker.js
Utilisez votre éditeur de code préféré et supprimez tout le contenu de src / index.css
. Ouvrez ensuite src / App.js
et réécrivez le code comme ceci:
importez React à partir de 'react';
const App = () => {
revenir (
Contact Manager
);
};
exporter l'application par défaut;
Exécutez le début du fil
à partir du répertoire react-contact-manager
pour démarrer le projet. Votre navigateur devrait ouvrir automatiquement http: // localhost: 3000
et vous devriez voir la rubrique «Contact Manager». Vérifiez rapidement l'onglet de la console pour vous assurer que le projet fonctionne correctement, sans avertissements ni erreurs, et si tout fonctionne correctement, utilisez Ctrl + C pour arrêter le serveur.
Création du serveur API avec Feathers
Continuons à générer l'API back-end pour notre projet CRUD à l'aide de l'outil feathers-cli
:
# Installer l'outil de ligne de commande Feathers
npm install @ feathersjs / cli -g
# Créer un répertoire pour le code back-end
# Exécutez cette commande dans le répertoire `react-contact-manager`
backend mkdir
cd backend
# Générer un serveur API back-end de plumes
les plumes génèrent une application
? Voulez-vous utiliser JavaScript ou TypeScript? Javascript
? Nom du projet backend
? Description contacts API serveur
? Dans quel dossier les fichiers source doivent-ils vivre? src
? Quel gestionnaire de packages utilisez-vous (doit être installé à l'échelle mondiale)? Fil
? Quel type d'API créez-vous? REST, en temps réel via Socket.io
? Quel cadre de test préférez-vous? Mocha + assert
? Cette application utilise l'authentification Non
# Assurez-vous que Mongodb est en cours d'exécution
sudo service mongod start
sudo service mongod status
● mongod.service - Serveur de base de données MongoDB
Chargé: chargé (/lib/systemd/system/mongod.service; désactivé; préréglage fournisseur: activé)
Actif: actif (en cours d'exécution) depuis le mercredi 2020-01-22 11:22:51 EAT; Il y a 6 ans
Documents: https://docs.mongodb.org/manual
PID principal: 13571 (mongod)
CGroup: /system.slice/mongod.service
└─13571 / usr / bin / mongod --config /etc/mongod.conf
# Générer des itinéraires RESTful pour le modèle de contact
les plumes génèrent du service
? De quel type de service s'agit-il? Mangouste
? Quel est le nom du service? Contacts
? Sur quel chemin le service doit-il être enregistré? /Contacts
? Qu'est-ce que la chaîne de connexion à la base de données? mongodb: // localhost: 27017 / contactsdb
# Installer l'e-mail et la validation de champ unique
fil ajouter une adresse de type mangouste
Ouvrons backend / config / default.json
. C'est ici que nous pouvons configurer nos paramètres de connexion MongoDB et d'autres paramètres. Modifiez la valeur de pagination par défaut à 50, car la pagination frontale ne sera pas traitée dans ce didacticiel:
{
"hôte": "localhost",
"port": 3030,
"public": "../public/",
"paginer": {
"par défaut": 50,
"max": 50
},
"mongodb": "mongodb: // localhost: 27017 / contactsdb"
}
Ouvrez backend / src / models / contact.model.js
et mettez à jour le code comme suit:
require ('mongoose-type-email');
module.exports = fonction (application) {
const modelName = 'contacts';
const mongooseClient = app.get ('mongooseClient');
const {Schema} = mongooseClient;
const schema = new Schema ({
Nom : {
premier: {
type: String,
requis: [true, 'First Name is required']
},
dernier: {
type: String,
requis: faux
}
},
email : {
type: mongooseClient.SchemaTypes.Email,
requis: [true, 'Email is required']
},
téléphone : {
type: String,
requis: [true, 'Phone is required'],
valider: {
validateur: fonction (v) {
return / ^ + (?: [0-9]?) {6,14} [0-9] $ /. test (v);
},
message: '{VALUE} n'est pas un numéro de téléphone international valide!'
}
}
}, {
horodatages: vrai
});
...
return mongooseClient.model (modelName, schema);
};
Mongoose introduit une nouvelle fonctionnalité appelée horodatages qui insère deux nouveaux champs pour vous – createdAt
et updatedAt
. Ces deux champs seront remplis automatiquement chaque fois que nous créerons ou mettrons à jour un enregistrement. Nous avons également installé le plug-in mongoose-type-email pour effectuer la validation des e-mails sur le serveur.
Maintenant, ouvrez backend / src / mongoose.js
et modifiez cette ligne:
{useCreateIndex: true, useNewUrlParser: true}
à:
{useCreateIndex: true, useNewUrlParser: true, useUnifiedTopology: true}
Ceci annulera un avertissement de dépréciation ennuyeux.
Ouvrez un nouveau terminal et exécutez le test de fil
dans le répertoire backend
. Vous devriez avoir tous les tests exécutés avec succès. Ensuite, continuez et exécutez fil start
pour démarrer le serveur principal. Une fois le serveur initialisé, il devrait imprimer 'L'application Feathers démarrée sur localhost: 3030'
sur la console.
Lancez votre navigateur et accédez à l'URL http: // localhost: 3030 / contacts . Vous devez vous attendre à recevoir la réponse JSON suivante:
{"total": 0, "limit": 50, "skip": 0, "data": []}
Testez l'API avec Postwoman
Utilisons maintenant Postwoman pour confirmer que tous nos terminaux fonctionnent correctement.
Commençons par créer un contact. Ce lien ouvrira Postwoman avec tout ce qui est configuré pour envoyer une demande POST au point de terminaison / contacts
. Assurez-vous que Entrée brute activée est réglé sur sur puis appuyez sur le bouton vert Envoyer pour créer un nouveau contact. La réponse devrait être quelque chose comme ceci:
{
"_id": "5e36f3eb8828f64ac1b2166c",
"Nom": {
"premier": "Tony",
"dernier": "Stark"
},
"téléphone": "+18138683770",
"email": "tony@starkenterprises.com",
"createdAt": "2020-02-02T16: 08: 11.742Z",
"updatedAt": "2020-02-02T16: 08: 11.742Z",
"__v": 0
}
Reprenons maintenant notre contact nouvellement créé. Ce lien ouvrira Postwoman prête à envoyer une demande GET au point de terminaison / contacts
. Lorsque vous appuyez sur le bouton Envoyer vous devriez obtenir une réponse comme celle-ci:
{
"total": 1,
"limite": 50,
"sauter": 0,
"données": [
{
"_id": "5e36f3eb8828f64ac1b2166c",
"name": {
"first": "Tony",
"last": "Stark"
},
"phone": "+18138683770",
"email": "tony@starkenterprises.com",
"createdAt": "2020-02-02T16:08:11.742Z",
"updatedAt": "2020-02-02T16:08:11.742Z",
"__v": 0
}
]
}
Nous pouvons afficher un contact individuel dans Postwoman en envoyant une demande GET à http: // localhost: 3030 / contacts /
. Le champ _id
sera toujours unique, vous devrez donc le copier de la réponse que vous avez reçue à l'étape précédente. C'est le lien pour l'exemple ci-dessus . Appuyez sur Envoyer pour afficher le contact.
Nous pouvons mettre à jour un contact en envoyant une demande PUT à http: // localhost: 3030 / contacts /
et en lui transmettant les données mises à jour comme JSON. C'est le lien pour l'exemple ci-dessus . En appuyant sur Envoyer le contact sera mis à jour.
Enfin, nous pouvons supprimer notre contact en envoyant une demande DELETE
à la même adresse, c'est-à-dire http: // localhost: 3030 / contacts /
. C'est le lien pour l'exemple ci-dessus . Appuyer sur Envoyer supprimera le contact.
Postwoman est un outil très polyvalent et je vous encourage à l'utiliser pour vous assurer que votre API fonctionne comme prévu, avant de passer à l'étape suivante. [19659058] Construire l'interface utilisateur
À l'origine, j'avais voulu utiliser UI sémantique pour le style, mais au moment de la rédaction, il n'a pas été mis à jour depuis plus d'un an. Heureusement, la communauté open source a réussi à maintenir le projet en vie en créant une fourchette populaire, Fomantic-UI et c'est ce que nous allons utiliser. Il est prévu de fusionner l'un dans l'autre lorsque le développement actif de l'interface utilisateur sémantique reprendra.
Nous utiliserons également React UI sémantique pour créer rapidement notre interface utilisateur sans avoir à définir de nombreux noms de classe. Heureusement, ce projet a également été mis à jour.
Enfin, nous utiliserons React Router pour gérer le routage.
Avec cela à l'écart, ouvrez un nouveau terminal dans le répertoire react-contact-manager
et entrez les commandes suivantes:
# Installer Fomantic UI CSS et Semantic UI React
fil ajouter fomantic-ui-css sémantique-ui-réagir
# Installer React Router
fil ajouter react-router-dom
Mettez à jour la structure du projet en ajoutant les répertoires et fichiers suivants au répertoire src
:
src
├── App.js
├── App.test.js
├── composants # (nouveau)
│ ├── contact-form.js # (nouveau)
│ └── contact-list.js # (nouveau)
├── index.css
├── index.js
├── pages # (nouveau)
│ ├── contact-form-page.js # (nouveau)
│ └── contact-list-page.js # (nouveau)
├── serviceWorker.js
└── setupTests.js
Du terminal:
cd src
Composants des pages mkdir
composants de contact / contact-form.js composants / contact-list.js
touch pages / contact-form-page.js pages / contact-list-page.js
Remplissons rapidement les fichiers JavaScript avec un code d'espace réservé.
Le composant ContactList
sera un composant fonctionnel (une fonction JavaScript simple qui renvoie un élément React):
// src / composants / contact-list.js
importer React à partir de 'react';
exporter la fonction par défaut ContactList () {
revenir (
Aucun contact ici
);
}
Pour les conteneurs de niveau supérieur, j'utilise des pages. Fournissons un code pour le composant ContactListPage
:
// src / pages / contact-list-page.js
importer React à partir de 'react';
importer la liste de contacts à partir de '../components/contact-list';
const ContactListPage = () => {
revenir (
Liste des contacts
);
};
exporter ContactListPage par défaut;
Le composant ContactForm
devra être intelligent, car il doit gérer son propre état, en particulier les champs de formulaire. Pour l'instant, nous allons utiliser ce code d'espace réservé:
// src / components / contact-form.js
importer React, {Component} à partir de 'react';
classe ContactForm étend Component {
render () {
revenir (
Formulaire en construction
)
}
}
exporter ContactForm par défaut;
Remplissez le composant ContactFormPage
avec ce code:
// src / pages / contact-form-page.js
importer React à partir de 'react';
importer ContactForm depuis '../components/contact-form';
const ContactFormPage = () => {
revenir (
);
};
exporter ContactFormPage par défaut;
Créons maintenant le menu de navigation et définissons les itinéraires pour notre application. App.js
est souvent appelé «modèle de mise en page» pour une application d'une seule page:
// src / App.js
importer React à partir de 'react';
importer {NavLink, Route} à partir de 'react-router-dom';
importer {Container} depuis 'semantic-ui-react';
importer ContactListPage à partir de './pages/contact-list-page';
importer ContactFormPage à partir de './pages/contact-form-page';
const App = () => {
revenir (
Liste de contacts
Ajouter un contact
);
};
exporter l'application par défaut;
Enfin, mettez à jour le fichier src / index.js
avec ce code, où nous importons Formantic-UI pour le style et BrowserRouter
pour utiliser l'API d'historique HTML5, qui conservera notre application synchronisée avec l'URL:
// src / index.js
importer React à partir de 'react';
importer ReactDOM depuis 'react-dom';
importer {BrowserRouter} depuis 'react-router-dom';
importer l'application depuis './App';
import 'fomantic-ui-css / semantic.min.css';
import './index.css';
ReactDOM.render (
,
document.getElementById ('root')
);
Assurez-vous que le serveur create-react-app
est toujours en cours d'exécution (sinon, démarrez-le en utilisant début de fil
), puis visitez http: // localhost: 3000 . Vous devriez avoir une vue similaire à la capture d'écran ci-dessous:
Gérer l'état avec React Hooks et l'API contextuelle
Auparavant, on pouvait avoir atteint Redux lors de la tâche de gestion de l'état dans une application React. Cependant, à partir de React v16.8.0, il est possible de gérer l'état global dans une application React en utilisant React Hooks and Context API .
En utilisant cette nouvelle technique, vous écrirez moins de code plus facile à gérer . Nous utiliserons toujours le modèle Redux, mais en utilisant simplement React Hooks et Context API .
La seule bibliothèque que nous installerons est axios :
le fil ajoute des axios
Voyons maintenant comment connecter l'API de contexte.
Définir un magasin de contexte
Ce sera comme notre magasin pour gérer l'état global des contacts. Notre état sera composé de plusieurs variables, dont un tableau de contacts
un état de chargement
et un objet message
pour stocker les messages d'erreur générés à partir de l'API principale
Dans le répertoire src
créez un dossier context
qui contient un fichier contact-context.js
:
cd src
contexte mkdir
touchez context / contact-context.js
Insérez également le code suivant:
import React, {useReducer, createContext} de 'react';
export const ContactContext = createContext ();
const initialState = {
contacts: [],
contact: {}, // sélectionné ou nouveau
message: {}, // {type: 'succès | échec', titre: 'Info | Erreur' contenu: 'lorem ipsum'}
};
réducteur de fonction (état, action) {
commutateur (action.type) {
cas 'FETCH_CONTACTS': {
revenir {
...Etat,
contacts: action.payload,
contact: {},
};
}
défaut:
lancer une nouvelle erreur ();
}
}
export const ContactContextProvider = props => {
const [state, dispatch] = useReducer (réducteur, initialState);
const {children} = props;
revenir (
{enfants}
);
};
Comme vous pouvez le voir, nous utilisons useReducer qui est une alternative à useState . useReducer
convient à la gestion d'une logique d'état complexe impliquant plusieurs sous-valeurs. Nous utilisons également l'API Context pour permettre le partage de données avec d'autres composants React.
Injection du fournisseur de contexte dans la racine d'application
Nous devons encapsuler notre composant racine avec le fournisseur de contexte
. Mettre à jour src / index.js
comme suit:
...
importer {ContactContextProvider} de './context/contact-context';
ReactDOM.render (
,
document.getElementById ('root')
);
Désormais, tous les composants enfants pourront accéder à l'état global à l'aide du crochet useContext
.
Dans cette étape, nous allons créer des données statiques à tester. Notre état initial a un tableau vide de contacts. Nous utiliserons la méthode de répartition
pour remplir temporairement le tableau de contacts
. Ouvrez pages / contact-list-page.js
et mettez à jour comme suit:
import React, {useContext, useEffect} de 'react';
importer la liste de contacts à partir de '../components/contact-list';
importer {ContactContext} depuis '../context/contact-context';
const data = [
{
_id: '1',
name: {
first: 'John',
last: 'Doe',
},
phone: '555',
email: 'john@gmail.com',
},
{
_id: '2',
name: {
first: 'Bruce',
last: 'Wayne',
},
phone: '777',
email: 'bruce.wayne@gmail.com',
},
];
exporter la fonction par défaut ContactListPage () {
const [state, dispatch] = useContext (ContactContext);
useEffect (() => {
envoi({
tapez: 'FETCH_CONTACTS',
charge utile: données,
});
}, [dispatch]);
revenir (
Liste des contacts
);
}
Ensuite, nous allons utiliser une simple boucle pour afficher les contacts dans components / contact-list.js
. Mettre à jour comme suit:
importer React à partir de 'react';
exporter la fonction par défaut ContactList ({contacts}) {
liste de const = () => {
retourner contacts.map (contact => {
revenir (
{contact.name.first} {contact.name.last}
);
});
};
revenir (
{list ()}
);
}
Maintenant, si vous revenez au navigateur, vous devriez avoir quelque chose comme ceci:
Rendons l'interface utilisateur de la liste plus attrayante en utilisant le style d'interface utilisateur sémantique . Dans le dossier src / components
créez un nouveau fichier contact-card.js
et copiez ce code:
// src / components / contact-card.js
importer React à partir de 'react';
importer {Carte, Bouton, Icône} depuis 'semantic-ui-react';
exporter la fonction par défaut ContactCard ({contact}) {
revenir (
{contact.name.first} {contact.name.last}
{contact.phone}
{contact.email}
);
}
Mettez à jour le composant ContactList
pour utiliser le nouveau composant ContactCard
:
// src / components / contact-list.js
importer React à partir de 'react';
importer {Card} depuis 'semantic-ui-react';
importer ContactCard depuis './contact-card';
exporter la fonction par défaut ContactList ({contacts}) {
cartes const = () => {
retourner contacts.map (contact => {
retour ;
});
};
retour {cards ()} ;
}
La page de liste devrait maintenant ressembler à ceci:
Récupérer des données de manière asynchrone à partir du serveur API Feathers
Maintenant que nous savons que l'état global est étant correctement partagé avec d'autres composants React, nous pouvons faire une véritable demande de récupération dans la base de données et utiliser les données pour remplir notre page de liste de contacts. Il existe plusieurs façons de le faire, mais la façon dont je vais vous montrer est étonnamment simple.
Tout d'abord, assurez-vous que la base de données Mongo et le serveur principal fonctionnent sur des terminaux distincts. Vous pouvez le confirmer en ouvrant l'URL http: // localhost: 3030 / contacts . S'il ne renvoie aucun résultat, dirigez-vous sur la page de sauvegarde et ajoutez-en un à l'aide de Postwoman.
Ensuite, mettez à jour src / contact-list-page.js
pour effectuer la demande d'extraction de données et utiliser ce résultat pour mettre à jour l'état global. Vous devrez supprimer la liste des tableaux de données statiques, car nous n'en aurons plus besoin. Mettez à jour le code comme suit:
import React, {useContext, useEffect} de 'react';
importer des axios depuis «axios»;
importer la liste de contacts à partir de '../components/contact-list';
importer {ContactContext} depuis '../context/contact-context';
exporter la fonction par défaut ContactListPage () {
const [state, dispatch] = useContext (ContactContext);
useEffect (() => {
const fetchData = async () => {
réponse const = attendre axios.get ('http: // localhost: 3030 / contacts');
envoi({
tapez: 'FETCH_CONTACTS',
charge utile: response.data.data || response.data, // si la pagination est désactivée
});
};
fetchData ();
}, [dispatch]);
revenir (
Liste des contacts
);
}
Après l'enregistrement, actualisez votre navigateur. La page de la liste de contacts doit maintenant afficher les données de la base de données.
Gestion des erreurs
Supposons que vous avez oublié de démarrer le serveur principal et le service de base de données Mongo. Si vous lancez le serveur create-react-app
la page d'accueil n'affichera simplement aucun contact. Cela n'indiquera pas qu'une erreur s'est produite, sauf si vous ouvrez l'onglet de la console.
Implémentons une gestion des erreurs en créant d'abord un composant qui affichera des messages d'erreur. Nous mettrons également en œuvre une fonction d'aide pour extraire les informations des erreurs détectées. Cette fonction d'assistance sera capable de faire la différence entre les erreurs réseau et les messages d'erreur envoyés par le serveur principal – par exemple, des messages de validation ou d'erreur 404.
Nous utiliserons le composant Semantic UI React's Message pour construire notre code. Créez un fichier flash-message.js
dans le dossier src / components
et insérez le code suivant:
import React from 'react';
importer {Message} de 'semantic-ui-react';
exporter la fonction par défaut FlashMessage ({message}) {
revenir (
);
}
fonction d'exportation flashErrorMessage (répartition, erreur) {
const err = error.response? error.response.data: erreur; // vérifie si une erreur de serveur ou de réseau
envoi({
tapez: 'FLASH_MESSAGE',
charge utile: {
tapez: «échouer»,
title: nom.err,
contenu: err.message,
},
});
}
Ensuite, ajoutez ce réducteur à src / context / contact-context.js
pour gérer les messages flash:
fonction reducer (état, action) {
commutateur (action.type) {
...
cas "FLASH_MESSAGE": {
revenir {
...Etat,
message: action.payload,
};
}
...
}
}
Enfin, mettez à jour pages / contact-list-page.js
. Nous allons implémenter un mécanisme try… catch
pour détecter et répartir les erreurs. Nous rendrons également le composant FlashMessage
qui s'affichera uniquement si un FLASH_MESSAGE
a été envoyé:
...
importer FlashMessage, {flashErrorMessage} depuis '../components/flash-message';
exporter la fonction par défaut ContactListPage () {
const [state, dispatch] = useContext (ContactContext);
useEffect (() => {
const fetchData = async () => {
essayez {
réponse const = attendre axios.get ('http: // localhost: 3030 / contacts');
envoi({
tapez: 'FETCH_CONTACTS',
charge utile: response.data.data || response.data, // si la pagination est désactivée
});
} catch (erreur) {
flashErrorMessage (répartition, erreur);
}
};
fetchData ();
}, [dispatch]);
revenir (
Liste des contacts
{state.message.content && }
);
}
Voici une capture d'écran d'un message d'erreur qui se produit lorsque le serveur principal est en cours d'exécution mais que le service de base de données Mongo a été arrêté:
Veuillez noter, afin de Pour récupérer à partir de l'erreur ci-dessus, vous devez d'abord démarrer le service Mongo, puis le serveur principal Feathers, dans cet ordre.
Gérer les demandes de création à l'aide des formulaires React Hook
Voyons ensuite comment ajouter de nouveaux contacts, et pour cela, nous avons besoin de formulaires. Au début, la création d'un formulaire semble assez facile. Mais lorsque nous commençons à penser à la validation côté client et à contrôler le moment où les erreurs doivent être affichées, cela devient délicat. De plus, le serveur principal effectue sa propre validation, et nous devrons également afficher ces erreurs sur le formulaire.
Plutôt que d'implémenter nous-mêmes toutes les fonctionnalités du formulaire, nous demanderons l'aide d'une bibliothèque de formulaires – React Hook Form – qui est actuellement la bibliothèque la plus facile à utiliser lors de la création de formulaires React. Nous utiliserons également le package classnames pour mettre en évidence les champs de formulaire avec des erreurs de validation.
Tout d'abord, arrêtez le serveur create-react-app
avec Ctrl + C et installer les packages suivants:
yarn add react-hook-form classnames
Redémarrez le serveur une fois l'installation des packages terminée.
Ajoutez cette classe CSS au fichier src / index.css
pour styliser les erreurs de formulaire:
.error {
couleur: # 9f3a38;
}
Ensuite, ouvrez src / components / contact-form.js
pour créer l'interface utilisateur du formulaire. Remplacez le code existant comme suit:
import React, {useContext} de 'react';
importer {Form, Grid, Button} depuis 'semantic-ui-react';
import {useForm} de 'react-hook-form';
importer des noms de classe à partir de 'classnames';
importer {ContactContext} depuis '../context/contact-context';
exporter la fonction par défaut ContactForm () {
const [state] = useContext (ContactContext);
const {register, errors, handleSubmit} = useForm ();
const onSubmit = data => console.log (data);
revenir (
Ajouter un nouveau contact
{errors.name &&
errors.name.first.type === 'requis' &&
'Vous devez fournir votre prénom'}
{errors.name &&
errors.name.first.type === 'minLength' &&
'Doit être composé de 2 caractères ou plus'}
{errors.phone &&
errors.phone.type === 'requis' &&
'Vous devez fournir un numéro de téléphone'}
{errors.phone &&
errors.phone.type === 'modèle' &&
'Le numéro de téléphone doit être au format international'}
{errors.email &&
errors.email.type === 'requis' &&
'Vous devez fournir une adresse e-mail'}
{errors.email &&
errors.email.type === 'modèle' &&
'Adresse e-mail invalide'}
);
}
Prenez le temps d'examiner le code; il se passe beaucoup de choses là-dedans. Consultez le guide de démarrage pour comprendre le fonctionnement de React Hook Form. Jetez également un œil à Documentation du formulaire Semantic UI React et voyez comment nous l'avons utilisé pour créer notre formulaire. Notez que pour notre gestionnaire onSubmit
nous envoyons des données de formulaire à la console.
Revenons maintenant au navigateur et essayons d'enregistrer intentionnellement un formulaire incomplet. En utilisant le menu de navigation que nous avons configuré plus tôt, cliquez sur le bouton Ajouter un contact puis appuyez sur le bouton Enregistrer sans remplir le formulaire. Cela devrait déclencher les messages d'erreur de validation suivants:
Vous pouvez maintenant commencer à remplir le formulaire. Au fur et à mesure que vous tapez, vous remarquerez que les différents messages de validation changent ou disparaissent. Une fois que tout est valide, vous pouvez appuyer à nouveau sur Enregistrer . Si vous vérifiez la sortie de votre console, vous devriez obtenir un objet JSON similaire à cette structure:
{
"Nom":{
"premier": "Jason",
"dernier": "Bourne"
},
"téléphone": "+1 555 555",
"email": "jason@gmail.com"
}
Définissons maintenant les actions nécessaires pour enregistrer un nouveau contact dans la base de données. Tout d'abord, spécifions un gestionnaire de réducteurs pour CREATE_CONTACT
. Mettre à jour src / context / contact-context.js
comme suit:
réducteur de fonction (état, action) {
commutateur (action.type) {
...
cas 'CREATE_CONTACT': {
revenir {
...Etat,
contacts: [...state.contacts, action.payload],
message: {
type: «succès»,
titre: «Succès»,
contenu: 'Nouveau contact créé!',
},
};
}
...
}
}
Ensuite, ouvrez src / components / contact-form.js
et mettez à jour le code comme suit:
import React, {useContext, useState} de 'react';
importer {Form, Grid, Button} depuis 'semantic-ui-react';
import {useForm} de 'react-hook-form';
importer des noms de classe à partir de 'classnames';
importer des axios depuis «axios»;
importer {Redirect} depuis 'react-router-dom';
importer {ContactContext} depuis '../context/contact-context';
import {flashErrorMessage} de './flash-message';
exporter la fonction par défaut ContactForm () {
const [state, dispatch] = useContext (ContactContext);
const {register, errors, handleSubmit} = useForm ();
const [redirect, setRedirect] = useState (false);
const createContact = async data => {
essayez {
réponse const = attendre axios.post ('http: // localhost: 3030 / contacts', données);
envoi({
tapez: 'CREATE_CONTACT',
charge utile: response.data,
});
setRedirect (true);
} catch (erreur) {
flashErrorMessage (répartition, erreur);
}
};
const onSubmit = async data => {
attendre createContact (données);
};
if (rediriger) {
retour ;
}
revenir (
// ... code de formulaire
)
}
Nous avons créé une fonction createContact
distincte pour gérer la création de nouveaux contacts. Plus tard, nous mettrons en œuvre une autre fonction pour mettre à jour les contacts existants. Si une erreur se produit, que ce soit une erreur de réseau ou de serveur, un message flash s'affiche pour indiquer à l'utilisateur ce qui ne va pas. Sinon, si la demande POST aboutit, une redirection vers /
sera effectuée. Un message de réussite s'affichera alors sur la page d'accueil.
Maintenant, finissez de remplir le formulaire. Après avoir cliqué sur Enregistrer nous devrions être dirigés vers la page de liste. Dans l'exemple ci-dessous, j'ai réussi à ajouter deux contacts.
Maintenant que nous pouvons ajouter de nouveaux contacts, voyons comment nous pouvons mettre à jour les contacts existants. Commençons par définir quelques réducteurs pour récupérer un seul contact et mettre à jour un contact.
Mettre à jour src / context / contact-context.js
comme suit:
fonction reducer (état, action) {
commutateur (action.type) {
...
cas 'FETCH_CONTACT': {
revenir {
...Etat,
contact: action.payload,
message: {},
};
}
cas 'UPDATE_CONTACT': {
const contact = action.payload;
revenir {
...Etat,
contacts: state.contacts.map (item =>
item._id === contact._id? contact: article,
),
message: {
type: «succès»,
title: 'Mise à jour réussie',
contenu: `Contact" $ {contact.email} "a été mis à jour!`,
},
};
}
...
}
}
Ensuite, convertissons le bouton Modifier du composant ContactCard
en un lien qui dirigera l'utilisateur vers le formulaire:
// src / components / contact-card .js
...
importer {Link} depuis 'react-router-dom';
exporter la fonction par défaut ContactCard ({contact, deleteContact}) {
revenir (
...
);
}
Désormais, lorsque les utilisateurs cliquent sur le lien Modifier l'URL devient http: // localhost: 3030 / contacts / edit / {id}
. Currently, the ContactFormPage
component hasn’t been built to handle such URLs. Let’s replace the existing code in the src/pages/contact-form-page.js
file with the following:
import React, { useContext, useEffect, useState } from 'react';
import axios from 'axios';
import ContactForm from '../components/contact-form';
import { flashErrorMessage } from '../components/flash-message';
import { ContactContext } from '../context/contact-context';
export default function ContactFormPage({ match }) {
const [state, dispatch] = useContext(ContactContext);
const [loading, setLoading] = useState(true);
useEffect(() => {
const { _id } = match.params; // Grab URL _id
if (_id) {
const fetchData = async () => {
try {
const response = await axios.get(
`http://localhost:3030/contacts/${_id}`,
);
dispatch({
type: 'FETCH_CONTACT',
payload: response.data,
});
setLoading(false);
} catch (error) {
flashErrorMessage(dispatch, error);
}
};
fetchData();
} else {
setLoading(false);
}
}, [match.params, dispatch]);
if (loading) {
return Please wait...
;
}
return (
);
}
When the page loads, it checks if an _id
exists in the URL. If there isn’t one, it will simply load a blank form which can be used to create a new contact. Otherwise, it will perform a fetch query and populate state.contact
via the dispatch
function.
We’ve also specified a local loading
state that’s set to true
by default. This is to delay rendering of the ContactForm
component until state.contact
has been populated. To understand why the delay is necessary, open src/components/contact-form.js
and update the code as follows:
...
export default function ContactForm({contact}) {
...
const { register, errors, handleSubmit } = useForm({
defaultValues: contact,
});
...
const updateContact = async data => {
try {
const response = await axios.patch(
`http://localhost:3030/contacts/${contact._id}`,
data,
);
dispatch({
type: 'UPDATE_CONTACT',
payload: response.data,
});
setRedirect(true);
} catch (error) {
flashErrorMessage(dispatch, error);
}
};
const onSubmit = async data => {
if (contact._id) {
await updateContact(data);
} else {
await createContact(data);
}
};
...
return (
//... Display Form Mode
{contact._id ? "Edit Contact" : "Add New Contact"}
....
);
}
As you can see above, we’ve introduced a new function for updating a contact. It’s almost identical to createContact
except that the URL is different and we’re using a PATCH
HTTP request. We’re also checking for the existence of _id
to determine if the form’s submit action should update or a create.
Back to the purpose of the loading
state, as you’re probably aware, React usually re-renders if data linked to a component via props changes. Unfortunately, passing an existing contact
to a React Hook Form can only be done during initialization. This means that, when the form first loads, it’s empty, as the fetch
function is asynchronous. By the time it resolves and populates the state.contact
field, the form will remain empty, as there’s no link between them.
One way to solve this problem is to write a function that will programmatically set the value of each field using the setValue
function. The other method which we’ve implemented is simply to delay rendering of the ContactForm
component until state.contact
has been populated.
Once the list page has finished refreshing, choose any contact and hit the Edit button.
Finish making your changes and hit save.
By now, your application should allow users to add new contacts and update existing ones.
Implement a Delete Request
Let’s now look at the final CRUD operation: delete. This one is much simpler to code. We start by implementing the DELETE_CONTACT
reducer in the src/context/contact-context.js
file:
function reducer(state, action) {
switch (action.type) {
...
case 'DELETE_CONTACT': {
const { _id, email } = action.payload;
return {
...state,
contacts: state.contacts.filter(item => item._id !== _id),
message: {
type: 'success',
title: 'Delete Successful',
content: `Contact "${email}" has been deleted!`,
},
};
}
...
}
}
Next, we implement the function that performs the actual deletion. We’ll do this in src/components/contact-card.js
. Update as follows:
...
import axios from 'axios';
import { ContactContext } from '../context/contact-context';
import { flashErrorMessage } from './flash-message';
const { useContext } = React;
export default function ContactCard({ contact }) {
// eslint-disable-next-line no-unused-vars
const [state, dispatch] = useContext(ContactContext);
const deleteContact = async id => {
try {
const response = await axios.delete(
`http://localhost:3030/contacts/${id}`,
);
dispatch({
type: 'DELETE_CONTACT',
payload: response.data,
});
} catch (error) {
flashErrorMessage(dispatch, error);
}
};
return (
...
...
);
}
Wait for the browser to refresh, then try to delete one or more contacts. The delete button should work as expected, with a confirmation message displayed at the top.
As a challenge, try to modify the delete button’s onClick
handler so that it asks the user to confirm or cancel the delete action.
Conclusion
We now have a complete application built, using React and Feathers, that can perform CREATE
READ
UPDATE
and DELETE
actions. Now that you understand the CRUD logic in a React application, you’re free to substitute technologies. For example, you can use a different CSS framework such as Bulma, Materialize or Bootstrap. You can also use a different back-end server such as LoopBack or a headless CMS platform such as Strapi.
I would also like to point out that the code we’ve written can be improved in many ways. For example, we can:
- replace hard-coded URLs with environment variables
- refactor code in certain places to make it cleaner
- add documentation via comments
- implement the reducer code in a separate file
- create an
actions
file and place all fetch related code there# - improve error handling by implementing user friendly messages
- write unit and end-to-end tests using modern testing frameworks
#You can decide not to do this and, instead, place action code next to where it’s being used. However, there are situations where action code could be called in more than one place. In that case, it’s recommended to move such code into a shareable action file.
If you’d like to learn more on how to build better information management applications, I recommend you learn the following:
GraphQL is a newer technology that replaces REST APIs. It allows front-end developers to query records that are joined. You can’t join records with REST API unless you write a custom route that executes a JOIN SQL/non-SQL query. Feathers does support GraphQL via a fgraphql
hook. Hence you can easily start using GraphQL on your front-end interface.
NextJS is a server-rendering framework that provides better SEO and website performance than is possible with create-react-app
. Combining these technologies, NextJS and Feathers with GraphQL support will allow you to build a robust data management application with less effort.
Source link