Fermer

mars 26, 2020

Créer une application CRUD Node.js en utilisant React et FeathersJS –29 minutes de lecture

ES6 en action: let et const


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.

 Un opérateur, assis à un standard téléphonique à l'ancienne - Créer une application CRUD en utilisant React, Redux et FeathersJS

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 / <_id>. 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 / <_id> 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 / <_id>. 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:

 Capture d'écran de la liste vide de contacts

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:

     Capture d'écran de la liste de contacts affichant deux contacts

    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:

     Les deux contacts rendus avec les styles ui sémantique

    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é:

     example-message-error

    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:

     Erreurs de validation côté client

    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.

     liste de contacts avec trois fiches de contact

    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 createContactexcept 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.

    Edit form displaying an existing contact

    Finish making your changes and hit save.

    List of edited contacts

    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 CREATEREADUPDATE 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