Создание сайтов в Горловке, ДНР. Создайте CRUD-приложение Node.js, используя React и FeathersJS

 
 

Создание современного проекта требует разделения логики на внешний и внутренний код. Причиной этого шага является повышение возможности повторного использования кода. Например, нам может понадобиться создать собственное мобильное приложение, которое обращается к внутреннему API. Или мы можем разрабатывать модуль, который станет частью большой модульной платформы.

Оператор, сидящий за старомодным телефонным коммутатором — создайте CRUD-приложение с использованием React, Redux и FeathersJS

Популярным способом создания серверного API является использование Node.js с такой библиотекой, как Express или Restify. Эти библиотеки упрощают создание маршрутов RESTful. Проблема с этими библиотеками заключается в том, что нам придется писать тонны повторяющегося кода. Нам также потребуется написать код для авторизации и другой логики промежуточного программного обеспечения.

Чтобы избежать этой дилеммы, мы можем использовать такую ​​структуру, как Feathers, которая поможет нам сгенерировать API всего за несколько команд.

Что делает Feathers удивительным, так это его простота. Вся структура является модульной, и нам нужно установить только те функции, которые нам нужны. Сам Feathers — это тонкая оболочка, построенная поверх Express, куда были добавлены новые функции — сервисы и хуки. Feathers также позволяет нам легко отправлять и получать данные через WebSockets.

Предпосылки

Чтобы следовать этому руководству, на вашем компьютере должны быть установлены следующие компоненты:

Node.js v12+ и актуальная версия npm. Ознакомьтесь с этим руководством, если вам нужна помощь в настройке.

МонгоБД v4.2+. Ознакомьтесь с этим руководством, если вам нужна помощь в настройке.

Менеджер пакетов Yarn — устанавливается с помощью npm i -g yarn.

Также будет полезно, если вы знакомы со следующими темами:

Как писать современный JavaScript

Управление потоком в современном JavaScript (например async... await)

Основы Реакта

Основы REST API

Также обратите внимание, что готовый код проекта вы можете найти на GitHub.

Подготовить приложение

Мы собираемся создать приложение для управления контактами CRUD, используя Node.js, React, Feathers и MongoDB.

В этом уроке я покажу вам, как создать приложение снизу вверх. Мы начнем наш проект с помощью популярного инструмента Create React App.

Вы можете установить его так:

npm install -g create-react-app

Затем создайте новый проект:

# scaffold a new react project

create-react-appreact-contact-manager

cd react-contact-manager

# delete unnecessary files

rm src/logo.svg src/App.css src/serviceWorker.js

Используйте свой любимый редактор кода и удалите все содержимое в файлах src/index.css. Затем откройте src/App.jsи перепишите код следующим образом:

import React from ‘react’;

const App = () => {

return (

 

 

Contact Manager

 

 

) ;

};

export default App;

И в src/index.js, измените код следующим образом:

import React from ‘react’;

import ReactDOM from ‘react-dom’;

import '. /index.css’;

import App from '. /App’;

ReactDOM.render (

,

document.getElementById (‘root’)

) ;

Запустите yarn startиз react-contact-managerкаталога, чтобы запустить проект. Ваш браузер должен автоматически открыть http: //localhost:3000, и вы должны увидеть заголовок «Диспетчер контактов». Быстро проверьте вкладку консоли, чтобы убедиться, что проект работает правильно, без предупреждений или ошибок, и, если все работает гладко, используйте Ctrl+, Cчтобы остановить сервер.

Создайте сервер API с Feathers

Давайте приступим к созданию внутреннего API для нашего проекта CRUD с помощью feathers-cliинструмента:

# 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

? Which coding style do you want to use? ESLint

# 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 Fri 2020-09-18 14:42:12 CEST; 4s ago

Docs: https://docs.mongodb.org/manual

Main PID: 31043 (mongod)

CGroup: /system.slice/mongod.service

└─31043 /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

Откроем backend/config/default.json. Здесь мы можем настроить параметры подключения к MongoDB и другие параметры. Измените значение разбивки на страницы по умолчанию на 50, так как интерфейсная разбивка на страницы не будет рассматриваться в этом руководстве:

{

«host»: «localhost»,

«port»: 3030,

«public»: «.../public/»,

«paginate»: {

«default»: 50,

«max»: 50

},

«mongodb»: «mongodb: //localhost:27017/contactsdb»

}

Откройте backend/src/models/contact.model.jsи обновите код следующим образом:

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

}) ;

// This is necessary to avoid model compilation errors in watch mode

// see https://mongoosejs.com/docs/api/connection.html#connection_Connection-deleteModel

if (mongooseClient.modelNames ().includes (modelName)) {

mongooseClient.deleteModel (modelName) ;

}

return mongooseClient.model (modelName, schema) ;

};

Mongoose представляет новую функцию под названием timestamps, которая вставляет для вас два новых поля — createdAtи updatedAt. Эти два поля будут заполняться автоматически всякий раз, когда мы создаем или обновляем запись. Мы также установили плагин mongoose-type-email для проверки электронной почты на сервере.

Теперь откройте backend/src/mongoose.jsи измените эту строку:

{ useCreateIndex: true, useNewUrlParser: true }

к:

{

useCreateIndex: true,

useNewUrlParser: true,

useUnifiedTopology: true,

useFindAndModify: false,

}

Это избавит вас от нескольких надоедливых предупреждений об устаревании.

Откройте новый терминал и выполните yarn testвнутри backendкаталога. Все тесты должны пройти успешно. Затем продолжайте и выполните yarn start, чтобы запустить внутренний сервер. После инициализации сервера он должен вывести ‘Feathers application started on localhost:3030‘на консоль.

Запустите браузер и перейдите по URL-адресу http: //localhost:3030/contacts. Вы должны ожидать получить следующий ответ JSON:

{«total»:0,«limit»:50,«skip»:0,«data»:[]}

Протестируйте API с помощью Hoppscotch

Теперь давайте используем Hoppscotch (ранее Postwoman), чтобы убедиться, что все наши конечные точки работают правильно.

Во-первых, давайте создадим контакт. Эта ссылка откроет Hoppscotch со всеми настройками для отправки POST-запроса на /contactsконечную точку. Убедитесь, что параметр «Необработанный ввод «включен, затем нажмите зеленую кнопку «Отправить «, чтобы создать новый контакт. Ответ должен быть примерно таким:

{

«_id»: «5f64832c20745f4f282b39f9»,

«name»: {

«first»: «Tony»,

«last»: «Stark»

},

«phone»: «+18138683770»,

«email»: «tony@starkenterprises.com»,

«createdAt»: «2020-09-18T09:51:40.021Z»,

«updatedAt»: «2020-09-18T09:51:40.021Z»,

«__v»: 0

}

Теперь давайте извлечем наш вновь созданный контакт. Эта ссылка откроет Hoppscotch, готовый отправить запрос GET на /contactsконечную точку. Когда вы нажмете кнопку «Отправить «, вы должны получить такой ответ:

{

«total»: 1,

«limit»: 50,

«skip»: 0,

«data»: [

{

«_id»: «5f64832c20745f4f282b39f9»,

«name»: {

«first»: «Tony»,

«last»: «Stark»

},

«phone»: «+18138683770»,

«email»: «tony@starkenterprises.com»,

«createdAt»: «2020-09-18T09:51:40.021Z»,

«updatedAt»: «2020-09-18T09:51:40.021Z»,

«__v»: 0

}

]

}

Мы можем показать отдельный контакт в Hoppscotch, отправив запрос GET на адрес http: //localhost:3030/contacts/<_id>. Поле _idвсегда будет уникальным, поэтому вам нужно скопировать его из ответа, полученного на предыдущем шаге. Это ссылка на приведенный выше пример. Нажатие «Отправить «покажет контакт.

Мы можем обновить контакт, отправив запрос PUT http: //localhost:3030/contacts/<_id>и передав ему обновленные данные в виде JSON. Это ссылка на приведенный выше пример. Нажатие «Отправить «обновит контакт.

Наконец, мы можем удалить наш контакт, отправив DELETEзапрос на тот же адрес, то есть http: //localhost:3030/contacts/<_id>. Это ссылка на приведенный выше пример. Нажатие «Отправить «удалит контакт.

Hoppscotch — очень универсальный инструмент, и я рекомендую вам использовать его, чтобы убедиться, что ваш API работает должным образом, прежде чем переходить к следующему шагу.

Создайте пользовательский интерфейс

Изначально я хотел использовать Semantic UI для оформления, но на момент написания статьи он не обновлялся более двух лет. К счастью, сообществу open-source удалось сохранить проект, создав популярную вилку Fomantic-UI, которую мы и будем использовать. Есть планы объединить одно с другим, когда возобновится активная разработка Semantic UI.

Мы также будем использовать Semantic UI React для быстрого создания нашего пользовательского интерфейса без необходимости определять множество имен классов. К счастью, этот проект также поддерживается в актуальном состоянии.

Наконец, мы будем использовать React Router для обработки маршрутизации.

После этого откройте новый терминал в react-contact-managerкаталоге и введите следующие команды:

# Install Fomantic UI CSS and Semantic UI React

yarn add fomantic-ui-csssemantic-ui-react

# Install React Router

yarn add react-router-dom

Обновите структуру проекта, добавив в каталог следующие каталоги и файлы src:

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

Из терминала:

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

Давайте быстро заполним файлы JavaScript некоторым кодом-заполнителем.

Компонент ContactListбудет функциональным компонентом (обычная функция JavaScript, которая возвращает элемент React):

// src/components/contact-list.js

import React from ‘react’;

const ContactList = () => {

return (

 

 

No contacts here

 

 

) ;

}

export default ContactList;

Для контейнеров верхнего уровня я использую страницы. Давайте предоставим некоторый код для ContactListPageкомпонента:

// 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;

Компонент ContactFormдолжен быть умным, так как он должен управлять своим состоянием, в частности формировать поля. Мы будем делать это с помощью хуков React:

// src/components/contact-form.js

import React from ‘react’;

const ContactForm = () => {

return (

 

 

Form under construction

 

 

)

}

export default ContactForm;

Заполните ContactFormPageкомпонент этим кодом:

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

import React from ‘react’;

import ContactForm from '.../components/contact-form’;

const ContactFormPage = () => {

return (

 

 

 

 

) ;

};

export default ContactFormPage;

Теперь давайте создадим меню навигации и определим маршруты для нашего приложения. App.jsчасто упоминается как «шаблон макета» для одностраничного приложения:

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

 

<NavLink

className="item"

activeClassName="active"

exact

to="/contacts/new"

>

Add Contact

 

 

 

 

) ;

};

export default App;

В приведенном выше коде используется React Router. Если вы хотите освежить в памяти этот вопрос, обратитесь к нашему руководству.

Наконец, обновите src/index.jsфайл с этим кодом, где мы импортируем Formantic-UI для стилей и BrowserRouterкомпонент для использования API истории HTML5, который будет синхронизировать наше приложение с 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’)

) ;

Убедитесь, что create-react-appсервер запущен (если нет, запустите его с помощью yarn start), затем посетите http: //localhost:3000. У вас должно получиться примерно то же, что и на скриншоте ниже:

Скриншот пустого списка контактов

Управление состоянием с помощью React Hooks и Context API

Раньше можно было использовать Redux, когда ему приходилось управлять состоянием в приложении React. Однако, начиная с React v16.8.0, можно управлять глобальным состоянием в приложении React с помощью React Hooks и Context API.

Используя эту новую технику, вы будете писать меньше кода, который легче поддерживать. Мы по-прежнему будем использовать паттерн Redux, но только с помощью React Hooks и Context API.

Далее давайте рассмотрим подключение Context API.

Определить хранилище контекста

Это будет похоже на наш магазин для обработки глобального состояния контактов. Наше состояние будет состоять из нескольких переменных, включая contactsмассив, loadingсостояние и messageобъект для хранения сообщений об ошибках, сгенерированных внутренним API-сервером.

В srcкаталоге создайте contextпапку, содержащую contact-context.jsфайл:

cd src

mkdir context

touch context/contact-context.js

И вставьте следующий код:

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,

};

}

default:

throw new Error () ;

}

}

export const ContactContextProvider = props => {

const [state, dispatch] = useReducer (reducer, initialState) ;

const { children } = props;

return (

{children}

 

) ;

};

Как видите, мы используем хук useReducer, который является альтернативой useState. useReducerподходит для обработки сложной логики состояния, включающей несколько подзначений. Мы также используем Context API, чтобы разрешить обмен данными с другими компонентами React.

Внедрить поставщика контекста в корень приложения

Нам нужно инкапсулировать наш корневой компонент с расширением Context Provider. Обновить src/index.jsследующим образом:

...

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

ReactDOM.render (

 

,

document.getElementById (‘root’)

) ;

Теперь все дочерние компоненты смогут получить доступ к глобальному состоянию с помощью useContextхука.

Показать список контактов

На этом этапе мы создадим некоторые статические данные для тестирования. Наше начальное состояние имеет пустой массив контактов. Мы будем использовать этот dispatchметод для временного заполнения contactsмассива. Откройте pages/contact-list-page.jsи обновите следующим образом:

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’,

},

];

const ContactListPage = () => {

const [state, dispatch] = useContext (ContactContext) ;

useEffect (() => {

dispatch ({

type: ‘FETCH_CONTACTS’,

payload: data,

}) ;

}, [dispatch]) ;

return (

 

 

List of Contacts

 

 

) ;

};

export default ContactListPage;

Далее мы будем использовать простой цикл для отображения контактов в формате components/contact-list.js. Обновить следующим образом:

import React from ‘react’;

const ContactList = ({ contacts }) => {

const list = () => {

return contacts.map (contact => {

return (

 

  •  

    {contact.name.first} {contact.name.last}

) ;

}) ;

};

return (

 

 

  • {list () }

 

 

) ;

}

export default ContactList;

Теперь, если вы вернетесь в браузер, у вас должно получиться что-то вроде этого:

Скриншот списка контактов с двумя контактами

Давайте сделаем пользовательский интерфейс списка более привлекательным, используя семантический стиль пользовательского интерфейса. В src/componentsпапке создайте новый файл contact-card.js:

touch src/components/contact-card.js

Затем добавьте следующий код:

// src/components/contact-card.js

import React from ‘react’;

import { Card, Button, Icon } from ‘semantic-ui-react’;

const ContactCard = ({ contact }) => {

return (

{contact.name.first} {contact.name.last}

 

{contact.phone}

{contact.email}

 

 

 

 

 

 

 

 

 

 

 

 

) ;

}

export default ContactCard;

Обновите ContactListкомпонент, чтобы использовать новый ContactCardкомпонент:

// src/components/contact-list.js

import React from ‘react’;

import { Card } from ‘semantic-ui-react’;

import ContactCard from '. /contact-card’;

const ContactList = ({ contacts }) => {

const cards = () => {

return contacts.map (contact => {

return ;

}) ;

};

return {cards () };

}

export default ContactList;

Теперь страница со списком должна выглядеть так:

Два контакта, отображаемые с помощью стилей семантического пользовательского интерфейса

Асинхронно получать данные с сервера API Feathers

Теперь, когда мы знаем, что глобальное состояние правильно передается другим компонентам React, мы можем сделать реальный запрос на выборку в базу данных и использовать данные для заполнения нашей страницы со списком контактов. Есть несколько способов сделать это, но способ, который я вам покажу, удивительно прост.

Во-первых, убедитесь, что и база данных Mongo, и внутренний сервер работают на разных терминалах. Вы можете подтвердить это, открыв URL-адрес http: //localhost:3030/contacts. Если он не дает никаких результатов, вернитесь на страницу и добавьте контакт с помощью Hoppscotch.

Затем установите библиотеку axios. Мы будем использовать это, чтобы сделать наши запросы:

yarn add axios

Затем обновите, src/contact-list-page.jsчтобы выполнить запрос на выборку данных, и используйте этот результат для обновления глобального состояния. Вам нужно будет удалить список статических массивов данных, так как он нам больше не понадобится. Обновите код следующим образом:

// src/contact-list-page.js

import React, { useContext, useEffect } from ‘react’;

import axios from ‘axios’;

import ContactList from '.../components/contact-list’;

import { ContactContext } from '.../context/contact-context’;

const 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

 

 

) ;

}

export default ContactListPage;

После сохранения вернитесь в браузер. На странице списка контактов теперь должны отображаться данные из базы данных.

Обработка ошибок

Предположим, вы забыли запустить внутренний сервер и службу базы данных Mongo. Если запустить create-react-appсервер, то на домашней странице просто не будет контактов. Это не будет означать, что произошла ошибка, если вы не откроете вкладку консоли.

Давайте реализуем некоторую обработку ошибок, сначала создав компонент, который будет отображать сообщения об ошибках. Мы также реализуем вспомогательную функцию для извлечения информации из перехваченных ошибок. Эта вспомогательная функция будет способна различать сетевые ошибки и сообщения об ошибках, отправленные внутренним сервером, например сообщения об ошибке проверки или ошибки 404.

Мы будем использовать компонент Semantic UI React Message для создания нашего кода. Создайте flash-message.jsфайл в src/componentsпапке:

touch src/components/flash-message.js

Затем вставьте следующий код:

// src/components/flash-message.js

import React from ‘react’;

import { Message } from ‘semantic-ui-react’;

export const FlashMessage = ({ message }) => {

return (

<Message

positive={message.type === ‘success’}

negative={message.type === ‘fail’}

header={message.title}

content={message.content}

/>

) ;

}

export const 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,

},

}) ;

}

Затем добавьте этот редюсер src/context/contact-context.jsдля обработки флеш-сообщений:

function reducer (state, action) {

switch (action.type) {

...

case ‘FLASH_MESSAGE’: {

return {

...state,

message: action.payload,

};

}

...

}

}

Наконец, обновите pages/contact-list-page.js. Мы реализуем try... catchмеханизм для перехвата и отправки ошибок. Мы также отобразим FlashMessageкомпонент, который будет отображаться только в том случае, если a FLASH_MESSAGEбыл отправлен:

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

...

import { FlashMessage, flashErrorMessage } from '.../components/flash-message’;

const 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 && }

 

 

) ;

}

export default ContactListPage;

Ниже приведен снимок экрана с сообщением об ошибке, которое возникает, когда внутренний сервер работает, но служба базы данных Mongo остановлена:

пример сообщения об ошибке

Обратите внимание, что для устранения вышеуказанной ошибки необходимо сначала запустить службу Mongo, а затем внутренний сервер Feathers в указанном порядке.

Обработка запросов на создание с использованием форм React Hook

Далее давайте посмотрим, как добавлять новые контакты, а для этого нам нужны формы. На первый взгляд построение формы кажется довольно простым. Но когда мы начинаем думать о проверке на стороне клиента и контроле того, когда должны отображаться ошибки, это становится сложным. Кроме того, внутренний сервер выполняет собственную проверку, и нам также потребуется отобразить эти ошибки в форме.

Вместо того, чтобы реализовывать всю функциональность форм самостоятельно, мы воспользуемся помощью библиотеки форм — React Hook Form — которая, на мой взгляд, является самой простой библиотекой для работы при создании форм React. Мы также будем использовать пакет classnames для выделения полей формы с ошибками проверки.

Прежде всего, остановите create-react-appсервер с помощью Ctrl+ Cи установите следующие пакеты:

yarn add react-hook-form classnames

Перезапустите сервер после завершения установки пакетов.

Добавьте этот класс CSS в src/index.cssфайл, чтобы стилизовать ошибки формы:

.error {

color: #9f3a38;

}

Затем откройте src/components/contact-form.jsдля создания пользовательского интерфейса формы. Замените существующий код следующим образом:

// src/components/contact-form.js

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’;

const ContactForm = () => {

const [state] = useContext (ContactContext) ;

const { register, errors, handleSubmit } = useForm () ;

const onSubmit = data => console.log (data) ;

return (

 

Add New Contact

 

 

 

 

First Name

<input

id="name.first"

name="name.first"

type="text"

placeholder="First Name"

ref={register ({ required: true, minLength: 2 }) }

/>

 

 

{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’}

 

 

 

Last Name

<input

id="name.last"

name="name.last"

type="text"

placeholder="Last Name"

ref={register}

/>

 

 

 

 

Phone

<input

id="phone"

name="phone"

type="text"

placeholder="Phone"

ref={register ({

required: true,

pattern: /^\+ (?:[0−9]?){6,14}[0−9]$/,

}) }

/>

 

 

{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’}

 

 

 

Email

<input

id="email"

name="email"

type="text"

placeholder="Email"

ref={register ({

required: true,

pattern: /^\w+ ([. -]? \w+) *@\w+ ([. -]? \w+) * (\. \w{2,3}) +$/,

}) }

/>

 

 

{errors.email &&

errors.email.type === ‘required’ &&

‘You need to provide an Email address’}

 

 

{errors.email &&

errors.email.type === ‘pattern’ &&

‘Invalid email address’}

 

 

 

 

 

 

 

 

) ;

}

export default ContactForm;

Потратьте время на изучение кода; там много всего происходит. См. руководство по началу работы, чтобы понять, как работает React Hook Form. Кроме того, взгляните на документацию формы Semantic UI React и посмотрите, как мы использовали ее для создания нашей формы. Обратите внимание, что в нашем onSubmitобработчике мы выводим данные формы на консоль.

Теперь вернемся в браузер и попробуем намеренно сохранить неполную форму. Используя меню навигации, которое мы настроили ранее, нажмите кнопку «Добавить контакт «, затем нажмите кнопку «Сохранить «, не заполняя форму. Это должно вызвать следующие сообщения об ошибках проверки:

Ошибки проверки на стороне клиента

Теперь вы можете приступить к заполнению формы. По мере ввода вы заметите, что различные сообщения проверки меняются или исчезают. Как только все будет верно, вы можете снова нажать «Сохранить «. Если вы проверите вывод своей консоли, вы должны получить объект JSON, подобный этой структуре:

{

«name»: {

«first»: «Jason»,

«last»: «Bourne»

},

«phone»: «+1 555 555»,

«email»: «jason@gmail.com»

}

Давайте теперь определим необходимые действия для сохранения нового контакта в базе данных. Во-первых, давайте укажем обработчик редьюсера для CREATE_CONTACT. Обновить src/context/contact-context.jsследующим образом:

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!',

},

};

}

...

}

}

Затем откройте src/components/contact-form.jsи обновите код следующим образом:

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’;

const 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

)

}

export default ContactForm;

Мы создали отдельную createContactфункцию для управления созданием новых контактов. Позже мы реализуем еще одну функцию для обновления существующих контактов. В случае возникновения ошибки, будь то ошибка сети или сервера, отобразится быстрое сообщение, указывающее пользователю, что пошло не так. В противном случае, если запрос POST будет успешным, /будет выполнено перенаправление на. После этого на главной странице появится сообщение об успешном завершении.

Теперь завершите заполнение формы. После нажатия «Сохранить «мы должны быть перенаправлены на страницу со списком. В приведенном ниже примере я успешно добавил еще два контакта.

список контактов с тремя карточками контактов

Редактировать существующие контакты

Теперь, когда мы можем добавлять новые контакты, давайте посмотрим, как мы можем обновить существующие контакты. Давайте начнем с определения пары редюсеров для извлечения одного контакта и обновления контакта.

Обновить src/context/contact-context.jsследующим образом:

function reducer (state, action) {

switch (action.type) {

...

case ‘FETCH_CONTACT’: {

return {

...state,

contact: action.payload,

};

}

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! `,

},

};

}

...

}

}

Далее преобразуем кнопку «РедактироватьContactCard «в компоненте в ссылку, которая будет направлять пользователя на форму:

// src/components/contact-card.js

...

import { Link } from ‘react-router-dom’;

const ContactCard = ({ contact }) => {

return (

...

 

 

<Button

basic

color="green"

as={Link}

to={`/contacts/edit/${contact. _id}`}

>

Edit

 

 

 

 

 

 

 

) ;

}

export default ContactCard;

Теперь, когда пользователи нажимают кнопку «Изменить «, URL-адрес изменится на http: //localhost:3030/contacts/edit/{id}. В настоящее время ContactFormPageкомпонент не предназначен для обработки таких URL-адресов. Давайте заменим существующий код в src/pages/contact-form-page.jsфайле следующим:

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’;

const 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 (

 

 

 

 

) ;

}

export default ContactFormPage;

Когда страница загружается, она проверяет, _idсуществует ли в URL-адресе. Если его нет, он просто загрузит пустую форму, которую можно использовать для создания нового контакта. В противном случае он выполнит запрос на выборку и заполнит данные state.contactс помощью dispatchфункции.

Мы также указали локальное loadingсостояние, которое установлено trueпо умолчанию. Это делается для того, чтобы отложить рендеринг ContactFormкомпонента до state.contactтех пор, пока он не будет заполнен. Чтобы понять, зачем нужна задержка, откройте src/components/contact-form.jsи обновите код следующим образом:

...

const 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»}

...

) ;

}

export default ContactForm;

Как вы можете видеть выше, мы ввели новую функцию для обновления контакта. Он почти идентичен createContact, за исключением того, что URL отличается, и мы используем PATCHHTTP-запрос. Мы также проверяем наличие, _idчтобы определить, должно ли действие отправки формы обновляться или создаваться.

Возвращаясь к назначению loadingсостояния, как вы, наверное, знаете, React обычно перерисовывает, если данные, связанные с компонентом через свойства, изменяются. К сожалению, передать существующую contactформу React Hook можно только во время инициализации. Это означает, что при первой загрузке формы она пуста, так как fetchфункция асинхронная. К тому времени, когда он разрешится и заполнит state.contactполе, форма останется пустой, так как между ними нет связи.

Один из способов решить эту проблему — написать функцию, которая будет программно устанавливать значение каждого поля с помощью setValueфункции. Другой метод, который мы реализовали, состоит в том, чтобы просто отложить рендеринг ContactFormкомпонента до state.contactтех пор, пока он не будет заполнен.

Когда страница со списком завершит обновление, выберите любой контакт и нажмите кнопку «Изменить «.

Редактировать форму, отображающую существующий контакт

Завершите внесение изменений и нажмите «Сохранить».

Список отредактированных контактов

К настоящему времени ваше приложение должно позволять пользователям добавлять новые контакты и обновлять существующие.

Реализовать запрос на удаление

Давайте теперь посмотрим на последнюю операцию CRUD: удаление. Этот намного проще кодировать. Начнем с реализации DELETE_CONTACTредюсера в src/context/contact-context.jsфайле:

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! `,

},

};

}

...

}

}

Далее мы реализуем функцию, которая выполняет фактическое удаление. Мы сделаем это в src/components/contact-card.js. Обновить следующим образом:

...

import axios from ‘axios’;

import { ContactContext } from '.../context/contact-context’;

import { flashErrorMessage } from '. /flash-message’;

const { useContext } = React;

const ContactCard = ({ contact }) => {

// eslint-disable-next-lineno-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 (

...

 

 

...

) ;

}

export default ContactCard;

Подождите, пока браузер обновится, затем попробуйте удалить один или несколько контактов. Кнопка удаления должна работать, как и ожидалось, с сообщением подтверждения, отображаемым вверху.

В качестве задачи попробуйте изменить onClickобработчик кнопки удаления, чтобы он запрашивал у пользователя подтверждение или отмену действия удаления.

Вывод

Теперь у нас есть готовое приложение, созданное с использованием React и Feathers, которое может выполнять действия CREATE, READи. Теперь, когда вы понимаете логику CRUD в приложении React, вы можете свободно заменять технологии. Например, вы можете использовать другую структуру CSS, такую ​​как Bulma, Materialize или Bootstrap. Вы также можете использовать другой внутренний сервер, такой как LoopBack, или безголовую платформу CMS, такую ​​как Strapi.UPDATEDELETE

Я также хотел бы отметить, что код, который мы написали, может быть улучшен многими способами. Например, мы можем:

заменить жестко закодированные URL-адреса переменными среды

рефакторить код в определенных местах, чтобы сделать его чище

добавить документацию через комментарии

реализовать код редуктора в отдельном файле

создайте actionsфайл и поместите туда весь код, связанный с выборкой #

улучшить обработку ошибок, внедрив удобные для пользователя сообщения

писать модульные и сквозные тесты с использованием современных фреймворков тестирования

# Вы можете решить не делать этого и вместо этого поместить код действия рядом с тем, где он используется. Однако бывают ситуации, когда код действия может быть вызван более чем в одном месте. В этом случае рекомендуется переместить такой код в общий файл действий.

Если вы хотите узнать больше о том, как создавать лучшие приложения для управления информацией, я рекомендую вам изучить следующее:

ГрафQL

Next.js

GraphQL — это более новая технология, которая заменяет REST API. Это позволяет разработчикам внешнего интерфейса запрашивать объединенные записи. Вы не можете объединять записи с помощью REST API, если не напишете собственный маршрут, который выполняет запрос JOIN SQL/не SQL. Feathers поддерживает GraphQL через fgraphqlхук, так что вы можете легко начать использовать GraphQL в своем внешнем интерфейсе.

Next.js — это фреймворк для рендеринга сервера, который обеспечивает лучшую SEO-оптимизацию и производительность веб-сайта, чем это возможно с create-react-app. Сочетание этих технологий, Next.js и Feathers с поддержкой GraphQL позволит вам создать надежное приложение для управления данными с меньшими усилиями.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая