React - это простая библиотека для создания интерактивных интерфейсных веб-приложений. Его набор функций является базовым. Он предоставляет вам архитектуру на основе компонентов для создания веб-приложений. Каждый компонент выполняет небольшую задачу в приложении, и их можно вкладывать друг в друга или размещать рядом. Родительские компоненты могут передавать данные дочерним компонентам, а дочерний компонент может вызывать функции, переданные от дочернего компонента. Однако он не позволяет осуществлять прямую связь между компонентами в любой другой конфигурации, и в нем нет встроенного решения для этого. Поэтому сторонние разработчики могут предпринять шаги для разработки решений для этого.

Одним из решений этой проблемы является MobX. Он позволяет установить состояние хранилища, которое затем может распространяться на любой компонент, который наблюдает изменения в хранилище.

В этой истории мы создадим приложение адресной книги, которое использует эти библиотеки, а также React Bootstrap, который имеет отличную интеграцию с этими библиотеками для создания форм. Для начала нам нужно запустить приложение Create React, чтобы сформировать приложение. Мы запускаем npx create-react-app address-book, чтобы создать папку проекта приложения с исходными файлами. У приложения будет домашняя страница для отображения контактов, и мы сможем открыть модальное окно для добавления контакта. Будет таблица, в которой отображаются все контакты, а также кнопки Изменить и Удалить в каждой строке для редактирования или удаления каждого контакта. Контакты будут храниться в центральном магазине MobX, чтобы хранить контакты в одном месте, что упрощает доступ к ним. React Router будет использоваться для маршрутизации. Контакты будут сохранены в серверной части, созданной с помощью пакета сервера JSON, расположенного по адресу https://github.com/typicode/json-server.

Для проверки формы вам необходимо использовать стороннюю библиотеку. Formik и Yup, расположенные по адресу https://github.com/jaredpalmer/formik и https://github.com/jquense/yup, отлично работают вместе, позволяя нам позаботиться о большинстве потребностей в проверке форм. Formik позволяет нам создавать формы и отображать ошибки, а также обрабатывать изменения значений формы, что является еще одним делом, в противном случае нам придется делать все мои руки. Ага, давайте напишем схему для проверки наших полей формы. Он может проверить практически все, с обычным кодом проверки, таким как электронная почта и обязательные поля, доступные как встроенные функции. Он также может проверять поля, которые зависят от других полей, например формат почтового индекса в зависимости от страны. Формы начальной загрузки можно без проблем использовать с Formik и Yup.

Как только это будет сделано, нам нужно установить некоторые библиотеки. Чтобы установить упомянутые выше библиотеки, мы запускаем npm i axios bootstrap formik react-bootstrap mobx mobx-react react-router-dom yup. Axios - это HTTP-клиент, который мы используем для выполнения HTTP-запросов к серверной части. react-router-dom - это имя пакета для последней версии React Router.

Теперь, когда у нас установлены все библиотеки, мы можем приступить к созданию приложения. Все файлы будут в папке src, если не указано иное. Сначала работаем над магазином MobX. Мы создаем файл с именем store.js в папке src и добавляем следующее:

import { observable, action, decorate } from "mobx";
class ContactsStore {
  contacts = [];
  setContacts(contacts) {
    this.contacts = contacts;
  }
}
ContactsStore = decorate(ContactsStore, {
  contacts: observable,
  setContacts: action,
});
export { ContactsStore };

Это простое хранилище, в котором хранятся контакты. В массиве contacts мы храним контакты для всего приложения. Функция setContacts позволяет нам устанавливать контакты из любого компонента, в который мы передаем объект this store.

Этот блок:

ContactsStore = decorate(ContactsStore, {
  contacts: observable,
  setContacts: action,
});

обозначает массив contacts в ContactsStore как объект, за которым компоненты могут следить на предмет изменений. setContacts обозначается как функция, которая может использоваться для установки contacts array в магазине.

В App.js мы заменяем существующее следующим:

import React from "react";
import { Router, Route } from "react-router-dom";
import HomePage from "./HomePage";
import { createBrowserHistory as createHistory } from "history";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";
import "./App.css";
const history = createHistory();
function App({ contactsStore }) {
  return (
    <div className="App">
      <Router history={history}>
        <Navbar bg="primary" expand="lg" variant="dark">
          <Navbar.Brand href="#home">Address Book App</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="mr-auto">
              <Nav.Link href="/">Home</Nav.Link>
            </Nav>
          </Navbar.Collapse>
        </Navbar>
        <Route
          path="/"
          exact
          component={props => (
            <HomePage {...props} contactsStore={contactsStore} />
          )}
        />
      </Router>
    </div>
  );
}
export default App;

Мы передаем хранилище любому компоненту, который в нем нуждается, например HomePage, который передаст компонент ContactForm.

Здесь мы добавляем панель навигации и показываем наши маршруты, проложенные React Router. В App.css мы заменяем существующий код на:

.App {
  text-align: center;
}

для центрирования текста.

Затем мы создаем нашу контактную форму. Это самая тяжелая часть нашего приложения. Мы создаем файл с именем ContactForm.js и добавляем:

import React from "react";
import { Formik } from "formik";
import Form from "react-bootstrap/Form";
import Col from "react-bootstrap/Col";
import InputGroup from "react-bootstrap/InputGroup";
import Button from "react-bootstrap/Button";
import * as yup from "yup";
import { COUNTRIES } from "./exports";
import PropTypes from "prop-types";
import { addContact, editContact, getContacts } from "./requests";
const schema = yup.object({
  firstName: yup.string().required("First name is required"),
  lastName: yup.string().required("Last name is required"),
  address: yup.string().required("Address is required"),
  city: yup.string().required("City is required"),
  region: yup.string().required("Region is required"),
  country: yup
    .string()
    .required("Country is required")
    .default("Afghanistan"),
  postalCode: yup
    .string()
    .when("country", {
      is: "United States",
      then: yup
        .string()
        .matches(/^[0-9]{5}(?:-[0-9]{4})?$/, "Invalid postal code"),
    })
    .when("country", {
      is: "Canada",
      then: yup
        .string()
        .matches(
          /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/,
          "Invalid postal code"
        ),
    })
    .required(),
  phone: yup
    .string()
    .when("country", {
      is: country => ["United States", "Canada"].includes(country),
      then: yup
        .string()
        .matches(/^[2-9]\d{2}[2-9]\d{2}\d{4}$/, "Invalid phone nunber"),
    })
    .required(),
  email: yup
    .string()
    .email("Invalid email")
    .required("Email is required"),
  age: yup
    .number()
    .required("Age is required")
    .min(0, "Minimum age is 0")
    .max(200, "Maximum age is 200"),
});
function ContactForm({
  edit,
  onSave,
  contact,
  onCancelAdd,
  onCancelEdit,
  contactsStore,
}) {
  const handleSubmit = async evt => {
    const isValid = await schema.validate(evt);
    if (!isValid) {
      return;
    }
    if (!edit) {
      await addContact(evt);
    } else {
      await editContact(evt);
    }
    const response = await getContacts();
    contactsStore.setContacts(response.data);
    onSave();
  };
return (
    <div className="form">
      <Formik
        validationSchema={schema}
        onSubmit={handleSubmit}
        initialValues={contact || {}}
      >
        {({
          handleSubmit,
          handleChange,
          handleBlur,
          values,
          touched,
          isInvalid,
          errors,
        }) => (
          <Form noValidate onSubmit={handleSubmit}>
            <Form.Row>
              <Form.Group as={Col} md="12" controlId="firstName">
                <Form.Label>First name</Form.Label>
                <Form.Control
                  type="text"
                  name="firstName"
                  placeholder="First Name"
                  value={values.firstName || ""}
                  onChange={handleChange}
                  isInvalid={touched.firstName && errors.firstName}
                />
                <Form.Control.Feedback type="invalid">
                  {errors.firstName}
                </Form.Control.Feedback>
              </Form.Group>
              <Form.Group as={Col} md="12" controlId="lastName">
                <Form.Label>Last name</Form.Label>
                <Form.Control
                  type="text"
                  name="lastName"
                  placeholder="Last Name"
                  value={values.lastName || ""}
                  onChange={handleChange}
                  isInvalid={touched.firstName && errors.lastName}
                />
<Form.Control.Feedback type="invalid">
                  {errors.lastName}
                </Form.Control.Feedback>
              </Form.Group>
              <Form.Group as={Col} md="12" controlId="address">
                <Form.Label>Address</Form.Label>
                <InputGroup>
                  <Form.Control
                    type="text"
                    placeholder="Address"
                    aria-describedby="inputGroupPrepend"
                    name="address"
                    value={values.address || ""}
                    onChange={handleChange}
                    isInvalid={touched.address && errors.address}
                  />
                  <Form.Control.Feedback type="invalid">
                    {errors.address}
                  </Form.Control.Feedback>
                </InputGroup>
              </Form.Group>
            </Form.Row>
            <Form.Row>
              <Form.Group as={Col} md="12" controlId="city">
                <Form.Label>City</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="City"
                  name="city"
                  value={values.city || ""}
                  onChange={handleChange}
                  isInvalid={touched.city && errors.city}
                />
<Form.Control.Feedback type="invalid">
                  {errors.city}
                </Form.Control.Feedback>
              </Form.Group>
              <Form.Group as={Col} md="12" controlId="region">
                <Form.Label>Region</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Region"
                  name="region"
                  value={values.region || ""}
                  onChange={handleChange}
                  isInvalid={touched.region && errors.region}
                />
                <Form.Control.Feedback type="invalid">
                  {errors.region}
                </Form.Control.Feedback>
              </Form.Group>
<Form.Group as={Col} md="12" controlId="country">
                <Form.Label>Country</Form.Label>
                <Form.Control
                  as="select"
                  placeholder="Country"
                  name="country"
                  onChange={handleChange}
                  value={values.country || ""}
                  isInvalid={touched.region && errors.country}
                >
                  {COUNTRIES.map(c => (
                    <option key={c} value={c}>
                      {c}
                    </option>
                  ))}
                </Form.Control>
                <Form.Control.Feedback type="invalid">
                  {errors.country}
                </Form.Control.Feedback>
              </Form.Group>
<Form.Group as={Col} md="12" controlId="postalCode">
                <Form.Label>Postal Code</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Postal Code"
                  name="postalCode"
                  value={values.postalCode || ""}
                  onChange={handleChange}
                  isInvalid={touched.postalCode && errors.postalCode}
                />
<Form.Control.Feedback type="invalid">
                  {errors.postalCode}
                </Form.Control.Feedback>
              </Form.Group>
<Form.Group as={Col} md="12" controlId="phone">
                <Form.Label>Phone</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Phone"
                  name="phone"
                  value={values.phone || ""}
                  onChange={handleChange}
                  isInvalid={touched.phone && errors.phone}
                />
<Form.Control.Feedback type="invalid">
                  {errors.phone}
                </Form.Control.Feedback>
              </Form.Group>
<Form.Group as={Col} md="12" controlId="email">
                <Form.Label>Email</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Email"
                  name="email"
                  value={values.email || ""}
                  onChange={handleChange}
                  isInvalid={touched.email && errors.email}
                />
<Form.Control.Feedback type="invalid">
                  {errors.email}
                </Form.Control.Feedback>
              </Form.Group>
<Form.Group as={Col} md="12" controlId="age">
                <Form.Label>Age</Form.Label>
                <Form.Control
                  type="text"
                  placeholder="Age"
                  name="age"
                  value={values.age || ""}
                  onChange={handleChange}
                  isInvalid={touched.age && errors.age}
                />
<Form.Control.Feedback type="invalid">
                  {errors.age}
                </Form.Control.Feedback>
              </Form.Group>
            </Form.Row>
            <Button type="submit" style={{ marginRight: "10px" }}>
              Save
            </Button>
            <Button type="button" onClick={edit ? onCancelEdit : onCancelAdd}>
              Cancel
            </Button>
          </Form>
        )}
      </Formik>
    </div>
  );
}
ContactForm.propTypes = {
  edit: PropTypes.bool,
  onSave: PropTypes.func,
  onCancelAdd: PropTypes.func,
  onCancelEdit: PropTypes.func,
  contact: PropTypes.object,
  contactsStore: PropTypes.object
};
export default ContactForm;

Мы передаем contactsStore из компонента HomePage, что позволяет нам использовать данные и функции из contactsStore.

Для проверки формы мы используем Formik, чтобы упростить создание нашей контактной формы здесь, с нашим компонентом Boostrap Form, вложенным в компонент Formik, чтобы мы могли использовать параметры Formik handleChange, handleSubmit, values, touched и errors. handleChange - это функция, которая позволяет нам обновлять данные поля формы из входных данных без написания кода самостоятельно. handleSubmit - это функция, которую мы передали в обработчик onSubmit компонента Formik. Параметр в функции - это данные, которые мы ввели, с именем поля в качестве ключа, как определено атрибутом name каждого поля и значением каждого поля в качестве значения этих ключей. Обратите внимание, что в каждой value опоре у нас есть ||'', поэтому мы не получаем неопределенные значения и предотвращаем появление неконтролируемых предупреждений формы.

Чтобы отображать сообщения проверки формы, мы должны передать свойство isInvalid каждому Form.Control компоненту. Объект schema - это то, что Formik будет проверять при проверке формы. Аргументом в функции required является сообщение об ошибке проверки. Второй аргумент функций matches, min и max также является сообщениями проверки.

Параметр функции ContactForm - это свойства, которые мы передадим из компонента HomePage, который мы построим позже. Функция handleSubmit проверяет, действительны ли данные, а затем, если это так, она переходит к сохранению в зависимости от того, добавляет ли она или редактирует контакт. Затем, когда сохранение будет успешным, мы устанавливаем контакты в магазине и вызываем onSave prop, которая является функцией для закрытия модального окна, в котором находится форма. Модальное окно будет определено на домашней странице.

Затем мы создаем файл с именем exports.js и помещаем:

export const COUNTRIES = [
  "Afghanistan",
  "Albania",
  "Algeria",
  "Andorra",
  "Angola",
  "Anguilla",
  "Antigua &amp; Barbuda",
  "Argentina",
  "Armenia",
  "Aruba",
  "Australia",
  "Austria",
  "Azerbaijan",
  "Bahamas",
  "Bahrain",
  "Bangladesh",
  "Barbados",
  "Belarus",
  "Belgium",
  "Belize",
  "Benin",
  "Bermuda",
  "Bhutan",
  "Bolivia",
  "Bosnia &amp; Herzegovina",
  "Botswana",
  "Brazil",
  "British Virgin Islands",
  "Brunei",
  "Bulgaria",
  "Burkina Faso",
  "Burundi",
  "Cambodia",
  "Cameroon",
  "Canada",
  "Cape Verde",
  "Cayman Islands",
  "Chad",
  "Chile",
  "China",
  "Colombia",
  "Congo",
  "Cook Islands",
  "Costa Rica",
  "Cote D Ivoire",
  "Croatia",
  "Cruise Ship",
  "Cuba",
  "Cyprus",
  "Czech Republic",
  "Denmark",
  "Djibouti",
  "Dominica",
  "Dominican Republic",
  "Ecuador",
  "Egypt",
  "El Salvador",
  "Equatorial Guinea",
  "Estonia",
  "Ethiopia",
  "Falkland Islands",
  "Faroe Islands",
  "Fiji",
  "Finland",
  "France",
  "French Polynesia",
  "French West Indies",
  "Gabon",
  "Gambia",
  "Georgia",
  "Germany",
  "Ghana",
  "Gibraltar",
  "Greece",
  "Greenland",
  "Grenada",
  "Guam",
  "Guatemala",
  "Guernsey",
  "Guinea",
  "Guinea Bissau",
  "Guyana",
  "Haiti",
  "Honduras",
  "Hong Kong",
  "Hungary",
  "Iceland",
  "India",
  "Indonesia",
  "Iran",
  "Iraq",
  "Ireland",
  "Isle of Man",
  "Israel",
  "Italy",
  "Jamaica",
  "Japan",
  "Jersey",
  "Jordan",
  "Kazakhstan",
  "Kenya",
  "Kuwait",
  "Kyrgyz Republic",
  "Laos",
  "Latvia",
  "Lebanon",
  "Lesotho",
  "Liberia",
  "Libya",
  "Liechtenstein",
  "Lithuania",
  "Luxembourg",
  "Macau",
  "Macedonia",
  "Madagascar",
  "Malawi",
  "Malaysia",
  "Maldives",
  "Mali",
  "Malta",
  "Mauritania",
  "Mauritius",
  "Mexico",
  "Moldova",
  "Monaco",
  "Mongolia",
  "Montenegro",
  "Montserrat",
  "Morocco",
  "Mozambique",
  "Namibia",
  "Nepal",
  "Netherlands",
  "Netherlands Antilles",
  "New Caledonia",
  "New Zealand",
  "Nicaragua",
  "Niger",
  "Nigeria",
  "Norway",
  "Oman",
  "Pakistan",
  "Palestine",
  "Panama",
  "Papua New Guinea",
  "Paraguay",
  "Peru",
  "Philippines",
  "Poland",
  "Portugal",
  "Puerto Rico",
  "Qatar",
  "Reunion",
  "Romania",
  "Russia",
  "Rwanda",
  "Saint Pierre &amp; Miquelon",
  "Samoa",
  "San Marino",
  "Satellite",
  "Saudi Arabia",
  "Senegal",
  "Serbia",
  "Seychelles",
  "Sierra Leone",
  "Singapore",
  "Slovakia",
  "Slovenia",
  "South Africa",
  "South Korea",
  "Spain",
  "Sri Lanka",
  "St Kitts &amp; Nevis",
  "St Lucia",
  "St Vincent",
  "St. Lucia",
  "Sudan",
  "Suriname",
  "Swaziland",
  "Sweden",
  "Switzerland",
  "Syria",
  "Taiwan",
  "Tajikistan",
  "Tanzania",
  "Thailand",
  "Timor L'Este",
  "Togo",
  "Tonga",
  "Trinidad &amp; Tobago",
  "Tunisia",
  "Turkey",
  "Turkmenistan",
  "Turks &amp; Caicos",
  "Uganda",
  "Ukraine",
  "United Arab Emirates",
  "United Kingdom",
  "United States",
  "United States Minor Outlying Islands",
  "Uruguay",
  "Uzbekistan",
  "Venezuela",
  "Vietnam",
  "Virgin Islands (US)",
  "Yemen",
  "Zambia",
  "Zimbabwe",
];

Это страны для поля стран в форме.

В HomePage.js мы помещаем:

import React from "react";
import { useState, useEffect } from "react";
import Table from "react-bootstrap/Table";
import ButtonToolbar from "react-bootstrap/ButtonToolbar";
import Button from "react-bootstrap/Button";
import Modal from "react-bootstrap/Modal";
import ContactForm from "./ContactForm";
import "./HomePage.css";
import { getContacts, deleteContact } from "./requests";
import { observer } from "mobx-react";
function HomePage({ contactsStore }) {
  const [openAddModal, setOpenAddModal] = useState(false);
  const [openEditModal, setOpenEditModal] = useState(false);
  const [initialized, setInitialized] = useState(false);
  const [selectedContact, setSelectedContact] = useState({});
  const openModal = () => {
    setOpenAddModal(true);
  };
  const closeModal = () => {
    setOpenAddModal(false);
    setOpenEditModal(false);
    getData();
  };
  const cancelAddModal = () => {
    setOpenAddModal(false);
  };
  const editContact = contact => {
    setSelectedContact(contact);
    setOpenEditModal(true);
  };
  const cancelEditModal = () => {
    setOpenEditModal(false);
  };
  const getData = async () => {
    const response = await getContacts();
    contactsStore.setContacts(response.data);
    setInitialized(true);
  };
  const deleteSelectedContact = async id => {
    await deleteContact(id);
    getData();
  };
  useEffect(() => {
    if (!initialized) {
      getData();
    }
  });
  return (
    <div className="home-page">
      <h1>Contacts</h1>
      <Modal show={openAddModal} onHide={closeModal}>
        <Modal.Header closeButton>
          <Modal.Title>Add Contact</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <ContactForm
            edit={false}
            onSave={closeModal.bind(this)}
            onCancelAdd={cancelAddModal}
            contactsStore={contactsStore}
          />
        </Modal.Body>
      </Modal>
      <Modal show={openEditModal} onHide={closeModal}>
        <Modal.Header closeButton>
          <Modal.Title>Edit Contact</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <ContactForm
            edit={true}
            onSave={closeModal.bind(this)}
            contact={selectedContact}
            onCancelEdit={cancelEditModal}
            contactsStore={contactsStore}
          />
        </Modal.Body>
      </Modal>
      <ButtonToolbar onClick={openModal}>
        <Button variant="outline-primary">Add Contact</Button>
      </ButtonToolbar>
      <br />
      <Table striped bordered hover>
        <thead>
          <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Address</th>
            <th>City</th>
            <th>Country</th>
            <th>Postal Code</th>
            <th>Phone</th>
            <th>Email</th>
            <th>Age</th>
            <th>Edit</th>
            <th>Delete</th>
          </tr>
        </thead>
        <tbody>
          {contactsStore.contacts.map(c => (
            <tr key={c.id}>
              <td>{c.firstName}</td>
              <td>{c.lastName}</td>
              <td>{c.address}</td>
              <td>{c.city}</td>
              <td>{c.country}</td>
              <td>{c.postalCode}</td>
              <td>{c.phone}</td>
              <td>{c.email}</td>
              <td>{c.age}</td>
              <td>
                <Button
                  variant="outline-primary"
                  onClick={editContact.bind(this, c)}
                >
                  Edit
                </Button>
              </td>
              <td>
                <Button
                  variant="outline-primary"
                  onClick={deleteSelectedContact.bind(this, c.id)}
                >
                  Delete
                </Button>
              </td>
            </tr>
          ))}
        </tbody>
      </Table>
    </div>
  );
}
export default observer(HomePage);

Обратите внимание, что у нас export default observer(HomePage); вместо export default HomePage;. Нам нужно обернуть HomePage вызовом функции observer, чтобы последние данные из хранилища были перенесены в этот компонент.

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

Обратите внимание, что мы передаем все свойства этого компонента компоненту ContactForm. Чтобы передать аргумент функции обработчика onClick, мы должны вызвать bind функции и передать аргумент функции в качестве второго аргумента bind. Например, в этом файле у нас есть editContact.bind(this, c), где c - контактный объект. Функция editContact определяется следующим образом:

const editContact = (contact) => {
    setSelectedContact(contact);
    setOpenEditModal(true);
  }

c - это параметр contact, который мы передаем.

Затем мы создаем файл с именем HomePage.css и помещаем:

.home-page {
  padding: 20px;
}

чтобы добавить отступ.

В index.js мы заменяем существующий код на:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { ContactsStore } from "./store";
const contactsStore = new ContactsStore();
ReactDOM.render(
  <App contactsStore={contactsStore} />,
  document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Затем мы создаем файл с именем requests.js и добавляем:

const APIURL = 'http://localhost:3000';
const axios = require('axios');
export const getContacts = () => axios.get(`${APIURL}/contacts`);
export const addContact = (data) => axios.post(`${APIURL}/contacts`, data);
export const editContact = (data) => axios.put(`${APIURL}/contacts/${data.id}`, data);
export const deleteContact = (id) => axios.delete(`${APIURL}/contacts/${id}`);

Это функции, которые отправляют наши HTTP-запросы в серверную часть для сохранения и удаления контактов.

Наконец, в public/index.html мы заменяем существующий код на:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <link rel="apple-touch-icon" href="logo192.png" />
  <link rel="manifest" crossorigin="use-credentials" href="%PUBLIC_URL%/manifest.json" />
<!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
  <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
  <title>React Address Book App</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
</head>
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
</body>
</html>

чтобы изменить заголовок и добавить таблицу стилей Bootstrap.

Теперь мы можем запустить приложение, запустив set PORT=3001 && react-scripts start в Windows или PORT=3006 react-scripts start в Linux.

Чтобы начать бэкенд, мы сначала устанавливаем пакет json-server, запустив npm i json-server. Их переходим в папку нашего проекта и запускаем:

json-server --watch db.json

В db.json измените текст на:

{
  "contacts": [
  ]
}

так что у нас есть contacts конечные точки, определенные в requests.js.

В итоге получаем следующее:

Подпишитесь на мою рассылку сейчас по адресу http://jauyeung.net/subscribe/.

Подпишитесь на меня в Twitter по адресу https://twitter.com/AuMayeung