Créer une application CRUD Node.js en utilisant React et FeathersJS – SitePoint


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 - Construisez une application CRUD en utilisant React, Redux et FeathersJS

La manière populaire de créer 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 un tonne de code répétitif. Nous devrons également écrire du code pour l’autorisation et d’autres logiques de middleware.

Pour échapper à ce dilemme, nous pouvons utiliser un cadre comme Plumes pour nous aider à générer une API en quelques commandes.

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 – prestations de service et crochets. Feathers nous permet également d’envoyer et de recevoir des données sans effort via WebSockets.

Conditions préalables

Pour suivre ce didacticiel, vous devez installer les éléments suivants sur votre ordinateur:

  • Node.js v12 + et une version à jour de npm. Consultez ce didacticiel si vous avez besoin d’aide pour vous installer.
  • MongoDB v4.2 +. Consultez ce didacticiel si vous avez besoin d’aide pour vous installer.
  • Fil gestionnaire de packages – installé à l’aide de npm i -g yarn.

Cela vous sera également utile si vous connaissez les sujets suivants:

  • comment écrire du JavaScript moderne
  • contrôle de flux en JavaScript moderne (par ex. async ... await)
  • les bases de Réagir
  • les bases de API REST

Veuillez également noter que vous pouvez trouver le code du projet terminé sur GitHub.

Échafaudez l’application

Nous allons créer une application de gestionnaire de contacts CRUD à l’aide de Node.js, React, Feathers et MongoDB.

Dans ce didacticiel, je vais vous montrer comment créer l’application de bas en haut. Nous lancerons notre projet à l’aide de l’outil de création d’application de réaction populaire.

Vous pouvez l’installer comme ceci:

npm install -g create-react-app

Créez ensuite un nouveau projet:

# scaffold a new react project
create-react-app react-contact-manager
cd react-contact-manager

# delete unnecessary files
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:

import React from 'react';

const App = () => {
  return (
    

Contact Manager

); }; export default App;

Courir yarn start du react-contact-manager pour démarrer le projet. Votre navigateur devrait s’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.

Construisez le serveur API avec des plumes

Continuons à générer l’API back-end pour notre projet CRUD en utilisant le feathers-cli outil:

# Install Feathers command-line tool
npm install @feathersjs/cli -g

# Create directory for the back-end code
# Run this command in the `react-contact-manager` directory
mkdir backend
cd backend

# Generate a feathers back-end API server
feathers generate app

? Do you want to use JavaScript or TypeScript? JavaScript
? Project name backend
? Description contacts API server
? What folder should the source files live in? src
? Which package manager are you using (has to be installed globally)? Yarn
? What type of API are you making? REST, Realtime via Socket.io
? Which testing framework do you prefer? Mocha + assert
? This app uses authentication No

# Ensure Mongodb is running
sudo service mongod start
sudo service mongod status

● mongod.service - MongoDB Database Server
   Loaded: loaded (/lib/systemd/system/mongod.service; disabled; vendor preset: enabled)
   Active: active (running) since Wed 2020-01-22 11:22:51 EAT; 6s ago
     Docs: https://docs.mongodb.org/manual
 Main PID: 13571 (mongod)
   CGroup: /system.slice/mongod.service
           └─13571 /usr/bin/mongod --config /etc/mongod.conf

# Generate RESTful routes for Contact Model
feathers generate service

? What kind of service is it? Mongoose
? What is the name of the service? contacts
? Which path should the service be registered on? /contacts
? What is the database connection string? mongodb://localhost:27017/contactsdb

# Install email and unique field validation
yarn add mongoose-type-email

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:

{
  "host": "localhost",
  "port": 3030,
  "public": "../public/",
  "paginate": {
    "default": 50,
    "max": 50
  },
  "mongodb": "mongodb://localhost:27017/contactsdb"
}

Ouvert backend/src/models/contact.model.js et mettez à jour le code comme suit:

require('mongoose-type-email');

module.exports = function (app) {
  const modelName = 'contacts';
  const mongooseClient = app.get('mongooseClient');
  const { Schema } = mongooseClient;
  const schema = new Schema({
    name : {
      first: {
        type: String,
        required: [true, 'First Name is required']
      },
      last: {
        type: String,
        required: false
      }
    },
    email : {
      type: mongooseClient.SchemaTypes.Email,
      required: [true, 'Email is required']
    },
    phone : {
      type: String,
      required: [true, 'Phone is required'],
      validate: {
        validator: function(v) {
          return /^+(?:[0-9] ?){6,14}[0-9]$/.test(v);
        },
        message: '{VALUE} is not a valid international phone number!'
      }
    }
  }, {
    timestamps: true
  });

  ...

  return  mongooseClient.model(modelName,  schema);
};

Mongoose introduit une nouvelle fonctionnalité appelée horodatages, qui insère pour vous deux nouveaux champs – 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 e-mail de type mangouste plugin pour effectuer la validation des e-mails sur le serveur.

Ouvert backend/src/mongoose.js et changez cette ligne:

{ useCreateIndex: true, useNewUrlParser: true }

à:

{ useCreateIndex: true, useNewUrlParser: true, useUnifiedTopology: true }

Cela annulera un avertissement de dépréciation ennuyeux.

Ouvrez un nouveau terminal et exécutez yarn test à l’intérieur de backend annuaire. Vous devriez avoir tous les tests exécutés avec succès. Ensuite, allez-y et exécutez yarn start pour démarrer le serveur principal. Une fois le serveur initialisé, il devrait imprimer 'Feathers application started on localhost:3030' à 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 /contacts point final. Assure-toi Entrée brute activée est réglé sur sur, puis appuyez sur le vert Envoyer pour créer un nouveau contact. La réponse devrait être quelque chose comme ceci:

{
  "_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
}

Récupérons maintenant notre nouveau contact. Ce lien ouvrira Postwoman prête à envoyer une demande GET au /contacts point final. Lorsque vous appuyez sur la touche Envoyer bouton, vous devriez obtenir une réponse comme celle-ci:

{
  "total": 1,
  "limit": 50,
  "skip": 0,
  "data": [
    {
      "_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 _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. Pressage Envoyer affichera le contact.

Nous pouvons mettre à jour un contact en envoyant une demande PUT à http://localhost:3030/contacts/<_id> et en lui passant les données mises à jour en JSON. C’est le lien pour l’exemple ci-dessus. Pressage Envoyer mettra à jour le contact.

Enfin, nous pouvons supprimer notre contact en envoyant un DELETE demande à la même adresse, c’est-à-dire http://localhost:3030/contacts/<_id>. C’est le lien pour l’exemple ci-dessus. Pressage 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.

Créer l’interface utilisateur

A 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 un fork 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 Réaction de l’interface utilisateur sémantique pour construire 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 react-contact-manager répertoire et entrez les commandes suivantes:

# Install Fomantic UI CSS and Semantic UI React
yarn add fomantic-ui-css semantic-ui-react

# Install React Router
yarn add react-router-dom

Mettez à jour la structure du projet en ajoutant les répertoires et fichiers suivants au src annuaire:

src
├── App.js
├── App.test.js
├── components #(new)
│   ├── contact-form.js #(new)
│   └── contact-list.js #(new)
├── index.css
├── index.js
├── pages #(new)
│   ├── contact-form-page.js #(new)
│   └── contact-list-page.js #(new)
├── serviceWorker.js
└── setupTests.js

Depuis le terminal:

cd src
mkdir pages components
touch components/contact-form.js components/contact-list.js
touch pages/contact-form-page.js pages/contact-list-page.js

Remplissons rapidement les fichiers JavaScript avec du code d’espace réservé.

le ContactList sera un composant fonctionnel (une simple fonction JavaScript qui renvoie un élément React):

// src/components/contact-list.js

import React from 'react';

export default function ContactList() {
  return (
    

No contacts here

); }

Pour les conteneurs de niveau supérieur, j’utilise des pages. Fournissons du code pour le ContactListPage composant:

// src/pages/contact-list-page.js

import React from 'react';
import ContactList from '../components/contact-list';

const ContactListPage = () => {
  return (
    

List of Contacts

); }; export default ContactListPage;

le ContactForm Le composant devra être intelligent, car il doit gérer son propre état, en particulier les champs de formulaire. Pour le moment, nous utiliserons ce code d’espace réservé:

// src/components/contact-form.js

import React, { Component } from 'react';

class ContactForm extends Component {
  render() {
    return (
      

Form under construction

) } } export default ContactForm;

Remplissez le ContactFormPage composant avec ce code:

// src/pages/contact-form-page.js

import React from 'react';
import ContactForm from '../components/contact-form';

const ContactFormPage = () => {
  return (
    
); }; export default ContactFormPage;

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

import React from 'react';
import { NavLink, Route } from 'react-router-dom';
import { Container } from 'semantic-ui-react';
import ContactListPage from './pages/contact-list-page';
import ContactFormPage from './pages/contact-form-page';

const App = () => {
  return (
    
      
Contacts List Add Contact
); }; export default App;

Enfin, mettez à jour src/index.js fichier avec ce code, où nous importons Formantic-UI pour le style et BrowserRouter pour utiliser l’API d’historique HTML5, qui gardera notre application synchronisée avec l’URL:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import 'fomantic-ui-css/semantic.min.css';
import './index.css';

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

Assurez-vous que le create-react-app serveur est toujours en cours d’exécution (sinon, démarrez-le en utilisant yarn start), 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 aurait pu atteindre Redux lorsqu’il était chargé de gérer 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 à l’aide de React Hooks et 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 le API de contexte.

La seule bibliothèque que nous installerons est axios:

yarn add axios

Voyons maintenant comment connecter l’API contextuelle.

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 contacts tableau, un loading état, et un message objet pour stocker les messages d’erreur générés à partir du serveur d’API principal.

dans le src répertoire, créez un context dossier contenant un contact-context.js fichier:

cd src
mkdir context
touch context/contact-context.js

Insérez également le code suivant:

import React, { useReducer, createContext } from 'react';

export const ContactContext = createContext();

const initialState = {
  contacts: [],
  contact: {}, // selected or new
  message: {}, // { type: 'success|fail', title:'Info|Error' content:'lorem ipsum'}
};

function reducer(state, action) {
  switch (action.type) {
    case 'FETCH_CONTACTS': {
      return {
        ...state,
        contacts: action.payload,
        contact: {},
      };
    }
    default:
      throw new Error();
  }
}

export const ContactContextProvider = props => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { children } = props;

  return (
    
      {children}
    
  );
};

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.

Injecter le fournisseur de contexte dans la racine de l’application

Nous devons encapsuler notre composant racine avec le Context Provider. Mise à jour src/index.js comme suit:

...
import { ContactContextProvider } from './context/contact-context';

ReactDOM.render(
  
    
      
    
  ,
  document.getElementById('root')
);

Tous les composants enfants pourront désormais accéder à l’état global à l’aide de useContext crochet.

Dans cette étape, nous allons créer des données statiques à tester. Notre état initial a un tableau vide de contacts. Nous utiliserons le dispatch méthode pour remplir temporairement le contacts tableau. Ouvert pages/contact-list-page.js et mettre à jour comme suit:

import React, { useContext, useEffect } from 'react';
import ContactList from '../components/contact-list';
import { ContactContext } from '../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',
  },
];

export default function ContactListPage() {
  const [state, dispatch] = useContext(ContactContext);

  useEffect(() => {
    dispatch({
      type: 'FETCH_CONTACTS',
      payload: data,
    });
  }, [dispatch]);

  return (
    

List of Contacts

); }

Ensuite, nous allons utiliser une simple boucle pour afficher les contacts dans components/contact-list.js. Mettez à jour comme suit:

import React from 'react';

export default function ContactList({ contacts }) {
  const list = () => {
    return contacts.map(contact => {
      return (
        
  • {contact.name.first} {contact.name.last}
  • ); }); }; return (
      {list()}
    ); }

    Maintenant, si vous revenez au navigateur, vous devriez avoir quelque chose comme ceci:

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

    Rendons l’interface utilisateur de la liste plus attrayante en utilisant le style d’interface utilisateur sémantique. dans le src/components dossier, créer un nouveau fichier contact-card.js et copiez ce code:

    // src/components/contact-card.js
    
    import React from 'react';
    import { Card, Button, Icon } from 'semantic-ui-react';
    
    export default function ContactCard({ contact }) {
      return (
        
          
            
               {contact.name.first} {contact.name.last}
            
            
              

    {contact.phone}

    {contact.email}

    ); }

    Mettre à jour le ContactList composant pour utiliser le nouveau ContactCard composant:

    // src/components/contact-list.js
    
    import React from 'react';
    import { Card } from 'semantic-ui-react';
    import ContactCard from './contact-card';
    
    export default function ContactList({ contacts }) {
      const cards = () => {
        return contacts.map(contact => {
          return ;
        });
      };
    
      return {cards()};
    }
    

    La page de liste devrait maintenant ressembler à ceci:

    Les deux contacts rendus avec les styles sémantique-ui

    Récupérer des données de manière asynchrone à partir du serveur API Feathers

    Maintenant que nous savons que l’état global est correctement partagé avec d’autres composants React, nous pouvons effectuer 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 procéder, 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, revenez sur la page 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 } from 'react';
    import axios from 'axios';
    import ContactList from '../components/contact-list';
    import { ContactContext } from '../context/contact-context';
    
    export default function ContactListPage() {
      const [state, dispatch] = useContext(ContactContext);
    
      useEffect(() => {
        const fetchData = async () => {
          const response = await axios.get('http://localhost:3030/contacts');
          dispatch({
            type: 'FETCH_CONTACTS',
            payload: response.data.data || response.data, // in case pagination is disabled
          });
        };
        fetchData();
      }, [dispatch]);
    
      return (
        

    List of Contacts

    ); }

    Après l’enregistrement, actualisez votre navigateur. La page de la liste de contacts devrait maintenant afficher les données de la base de données.

    La gestion des erreurs

    Supposons que vous ayez oublié de démarrer le serveur principal et le service de base de données Mongo. Si vous lancez le create-react-app serveur, 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, les messages de validation ou d’erreur 404.

    Nous utiliserons Message de l’interface utilisateur sémantique React composant pour construire notre code. Créer un flash-message.js fichier dans le src/components dossier et insérez le code suivant:

    import React from 'react';
    import { Message } from 'semantic-ui-react';
    
    export default function FlashMessage({ message }) {
      return (
        
      );
    }
    
    export function flashErrorMessage(dispatch, error) {
      const err = error.response ? error.response.data : error; // check if server or network error
      dispatch({
        type: 'FLASH_MESSAGE',
        payload: {
          type: 'fail',
          title: err.name,
          content: err.message,
        },
      });
    }
    

    Ensuite, ajoutez ce réducteur à src/context/contact-context.js pour gérer les messages flash:

    function reducer(state, action) {
      switch  (action.type)  {
        ...
        case 'FLASH_MESSAGE': {
          return {
            ...state,
            message: action.payload,
          };
        }
        ...
      }
    }
    

    Enfin, mettez à jour pages/contact-list-page.js. Nous mettrons en œuvre un try … catch mécanisme de détection et de répartition des erreurs. Nous rendrons également le FlashMessage composant qui s’affiche uniquement si un FLASH_MESSAGE a été expédié:

    ...
    import FlashMessage, { flashErrorMessage } from '../components/flash-message';
    
    export default function ContactListPage() {
      const [state, dispatch] = useContext(ContactContext);
    
      useEffect(() => {
        const fetchData = async () => {
          try {
            const response = await axios.get('http://localhost:3030/contacts');
            dispatch({
              type: 'FETCH_CONTACTS',
              payload: response.data.data || response.data, // in case pagination is disabled
            });
          } catch (error) {
            flashErrorMessage(dispatch, error);
          }
        };
        fetchData();
      }, [dispatch]);
    
      return (
        

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

    exemple de message d'erreur

    Veuillez noter que pour récupérer 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 ce faire, 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. En outre, le serveur principal effectue sa propre validation et nous devons également afficher ces erreurs sur le formulaire.

    Plutôt que d’implémenter nous-mêmes toutes les fonctionnalités du formulaire, nous ferons appel à une bibliothèque de formulaires – Formulaire React Hook – qui est actuellement la bibliothèque la plus facile à utiliser lors de la création de formulaires React. Nous utiliserons également le noms de classe package pour mettre en évidence les champs de formulaire avec des erreurs de validation.

    Tout d’abord, arrêtez le create-react-app serveur avec Ctrl + C et installez 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 à src/index.css fichier pour styliser les erreurs de formulaire:

    .error {
      color: #9f3a38;
    }
    

    Ensuite, ouvrez src/components/contact-form.js pour construire l’interface utilisateur du formulaire. Remplacez le code existant comme suit:

    import React, { useContext } from 'react';
    import { Form, Grid, Button } from 'semantic-ui-react';
    import { useForm } from 'react-hook-form';
    import classnames from 'classnames';
    import { ContactContext } from '../context/contact-context';
    
    export default function ContactForm() {
      const [state] = useContext(ContactContext);
      const { register, errors, handleSubmit } = useForm();
      const onSubmit = data => console.log(data);
    
      return (
        
          
            

    Add New Contact

    {errors.name && errors.name.first.type === 'required' && 'You need to provide First Name'} {errors.name && errors.name.first.type === 'minLength' && 'Must be 2 or more characters'} {errors.phone && errors.phone.type === 'required' && 'You need to provide a Phone number'} {errors.phone && errors.phone.type === 'pattern' && 'Phone number must be in International format'} {errors.email && errors.email.type === 'required' && 'You need to provide an Email address'} {errors.email && errors.email.type === 'pattern' && 'Invalid email address'}
    ); }

    Prenez le temps d’examiner le code; il se passe beaucoup de choses là-dedans. Voir 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 pour notre onSubmit gestionnaire, nous sortons les données du formulaire vers 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 Ajouter le contact puis appuyez sur le bouton sauvegarder bouton 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. Lorsque vous tapez, vous remarquerez que les différents messages de validation changent ou disparaissent. Une fois que tout est valide, vous pouvez appuyer sur sauvegarder encore. Si vous vérifiez la sortie de votre console, vous devriez obtenir un objet JSON similaire à cette structure:

    {
      "name":{
        "first": "Jason",
        "last": "Bourne"
      },
      "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éducteur pour CREATE_CONTACT. Mise à jour src/context/contact-context.js comme suit:

    function reducer(state, action) {
      switch  (action.type)  {
        ...
        case 'CREATE_CONTACT': {
          return {
            ...state,
            contacts: [...state.contacts, action.payload],
            message: {
              type: 'success',
              title: 'Success',
              content: 'New Contact created!',
            },
          };
        }
        ...
      }
    }
    

    Ensuite, ouvrez src/components/contact-form.js et mettez à jour le code comme suit:

    import React, { useContext, useState } from 'react';
    import { Form, Grid, Button } from 'semantic-ui-react';
    import { useForm } from 'react-hook-form';
    import classnames from 'classnames';
    import axios from 'axios';
    import { Redirect } from 'react-router-dom';
    import { ContactContext } from '../context/contact-context';
    import { flashErrorMessage } from './flash-message';
    
    export default function ContactForm() {
      const [state, dispatch] = useContext(ContactContext);
      const { register, errors, handleSubmit } = useForm();
      const [redirect, setRedirect] = useState(false);
    
      const createContact = async data => {
        try {
          const response = await axios.post('http://localhost:3030/contacts', data);
          dispatch({
            type: 'CREATE_CONTACT',
            payload: response.data,
          });
          setRedirect(true);
        } catch (error) {
          flashErrorMessage(dispatch, error);
        }
      };
    
      const onSubmit = async data => {
        await createContact(data);
      };
    
      if (redirect) {
        return ;
      }
    
      return (
        //... form code
      )
    }
    

    Nous avons créé un autre createContact 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 requête POST aboutit, une redirection vers / sera réalisée. Un message de réussite sera alors affiché sur la page d’accueil.

    Maintenant, finissez de remplir le formulaire. Après avoir cliqué sauvegarder, nous devons être dirigés vers la page de liste. Dans l’exemple ci-dessous, j’ai réussi à ajouter deux contacts supplémentaires.

    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.

    Mise à jour src/context/contact-context.js comme suit:

    function reducer(state, action) {
      switch  (action.type)  {
        ...
        case 'FETCH_CONTACT': {
          return {
            ...state,
            contact: action.payload,
            message: {},
          };
        }
        case 'UPDATE_CONTACT': {
          const contact = action.payload;
          return {
            ...state,
            contacts: state.contacts.map(item =>
              item._id === contact._id ? contact : item,
            ),
            message: {
              type: 'success',
              title: 'Update Successful',
              content: `Contact "${contact.email}" has been updated!`,
            },
          };
        }
        ...
      }
    }
    

    Ensuite, convertissons le Éditer bouton dans le ContactCard composant vers un lien qui dirigera l’utilisateur vers le formulaire:

    // src/components/contact-card.js
    
    ...
    import { Link } from 'react-router-dom';
    
    export default function ContactCard({contact, deleteContact}) {
      return (
        
          ...
          
            
    ); }

    Maintenant, lorsque les utilisateurs cliquent sur le Éditer lien, l’URL deviendra http://localhost:3030/contacts/edit/{id}. Actuellement, le ContactFormPage Le composant n’a pas été conçu pour gérer ces URL. Remplaçons le code existant dans le src/pages/contact-form-page.js fichier avec les éléments suivants:

    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 (
    ); }

    Lorsque la page se charge, elle vérifie si un _id existe dans l’URL. S’il n’y en a pas, il charge simplement un formulaire vierge qui peut être utilisé pour créer un nouveau contact. Sinon, il effectuera une requête d’extraction et remplira state.contact via le dispatch une fonction.

    Nous avons également spécifié un local loading état qui est réglé sur true par défaut. C’est pour retarder le rendu du ContactForm composant jusqu’à state.contact a été rempli. Pour comprendre pourquoi le délai est nécessaire, ouvrez src/components/contact-form.js et mettez à jour le code comme suit:

    ...
    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"}

    .... ); }

    Comme vous pouvez le voir ci-dessus, nous avons introduit une nouvelle fonction pour mettre à jour un contact. C’est presque identique à createContact, sauf que l’URL est différente et que nous utilisons un PATCH Requête HTTP. Nous vérifions également l’existence de _id pour déterminer si l’action d’envoi du formulaire doit être mise à jour ou créée.

    Retour à l’objectif de la loading comme vous le savez probablement, React restitue généralement si les données liées à un composant via des accessoires changent. Malheureusement, passer un existant contact à un formulaire React Hook ne peut être fait que pendant initialisation. Cela signifie que, lors du premier chargement du formulaire, il est vide, car fetch la fonction est asynchrone. Au moment où il résout et remplit le state.contact , le formulaire restera vide, car il n’y a aucun lien entre eux.

    Une façon de résoudre ce problème consiste à écrire une fonction qui définira par programme la valeur de chaque champ à l’aide du paramètre setValue une fonction. L’autre méthode que nous avons implémentée consiste simplement à retarder le rendu du ContactForm composant jusqu’à state.contact a été rempli.

    Une fois que la page de liste a terminé son rafraîchissement, choisissez un contact et appuyez sur le bouton Éditer bouton.

    Modifier le formulaire affichant un contact existant

    Terminez vos modifications et appuyez sur Enregistrer.

    Liste des contacts modifiés

    À présent, votre application devrait permettre aux utilisateurs d’ajouter de nouveaux contacts et de mettre à jour ceux qui existent déjà.

    Mettre en œuvre une demande de suppression

    Voyons maintenant l’opération CRUD finale: supprimer. Celui-ci est beaucoup plus simple à coder. Nous commençons par mettre en œuvre le DELETE_CONTACT réducteur dans le src/context/contact-context.js fichier:

    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!`,
            },
          };
        }
        ...
      }
    }
    

    Ensuite, nous implémentons la fonction qui effectue la suppression réelle. Nous le ferons en src/components/contact-card.js. Mettez à jour comme suit:

    ...
    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 (
        ...
         
        ...
      );
    }
    

    Attendez que le navigateur soit actualisé, puis essayez de supprimer un ou plusieurs contacts. Le bouton Supprimer doit fonctionner comme prévu, avec un message de confirmation affiché en haut.

    Comme défi, essayez de modifier le bouton de suppression onClick pour qu’il demande à l’utilisateur de confirmer ou d’annuler l’action de suppression.

    Conclusion

    Nous avons maintenant une application complète construite, utilisant React et Feathers, qui peut effectuer CREATE, READ, UPDATE et DELETE Actions. Maintenant que vous comprenez la logique CRUD dans une application React, vous êtes libre de remplacer les technologies. Par exemple, vous pouvez utiliser un framework CSS différent tel que Bulma, Materialise ou Bootstrap. Vous pouvez également utiliser un autre serveur principal tel que LoopBack ou une plateforme CMS sans tête telle que Strapi.

    Je voudrais également souligner que le code que nous avons écrit peut être amélioré de plusieurs façons. Par exemple, nous pouvons:

    • remplacer les URL codées en dur par des variables d’environnement
    • refactoriser le code à certains endroits pour le rendre plus propre
    • ajouter de la documentation via des commentaires
    • implémenter le code réducteur dans un fichier séparé
    • créé un actions déposer et y placer tout le code associé à la récupération#
    • améliorer la gestion des erreurs en mettant en œuvre des messages conviviaux
    • écrire des tests unitaires et de bout en bout à l’aide de cadres de test modernes

    #Vous pouvez décider de ne pas le faire et, au lieu de cela, placer le code d’action à côté de l’endroit où il est utilisé. Cependant, il existe des situations où le code d’action peut être appelé à plusieurs endroits. Dans ce cas, il est recommandé de déplacer ce code dans un fichier d’action partageable.

    Si vous souhaitez en savoir plus sur la façon de créer de meilleures applications de gestion des informations, je vous recommande d’apprendre ce qui suit:

    GraphQL est une technologie plus récente qui remplace les API REST. Il permet aux développeurs front-end d’interroger les enregistrements joints. Vous ne pouvez pas joindre des enregistrements avec l’API REST à moins d’écrire un itinéraire personnalisé qui exécute une requête JOIN SQL / non SQL. Plumes prend en charge GraphQL par l’intermédiaire d’un fgraphql crochet. Par conséquent, vous pouvez facilement commencer à utiliser GraphQL sur votre interface frontale.

    NextJS est un cadre de rendu de serveur qui offre de meilleures performances de référencement et de site Web qu’avec create-react-app. En combinant ces technologies, NextJS et Feathers avec la prise en charge de GraphQL vous permettront de créer une application de gestion de données robuste avec moins d’effort.

    Close Menu