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

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

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

Мы используем react-google-maps для включения Google Maps в наше приложение React, расположенное по адресу https://www.npmjs.com/package/react-google-maps. Он популярен и активно поддерживается, поэтому будет работать с последней версией React. Он также прост в использовании.

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

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

import { SET_CONTACTS } from './actions';
const setContacts = (contacts) => {
    return {
        type: SET_CONTACTS,
        payload: contacts
    }
};
export { setContacts };

Это создатель действия для создания действия для сохранения контактов в магазине.

Мы создаем еще один файл с именем actions.js и добавляем:

const SET_CONTACTS = 'SET_CONTACTS';
export { SET_CONTACTS };

Это просто константа типа для отправки действия.

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

import React from 'react';
import { Router, Route, Link } 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() {
  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={HomePage} />
      </Router>
    </div>
  );
}
export default App;

Здесь мы добавляем панель навигации и показываем наши маршруты, проложенные 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';
import { connect } from 'react-redux';
import { setContacts } from './actionCreators';
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,
  setContacts,
  contact,
  onCancelAdd,
  onCancelEdit,
}) {
  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();
    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
}
const mapStateToProps = state => {
  return {
    contacts: state.contacts,
  }
}
const mapDispatchToProps = dispatch => ({
  setContacts: contacts => dispatch(setContacts(contacts))
})
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ContactForm);

Мы используем 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, которая является функцией для закрытия модального окна, в котором находится форма. Модальное окно будет определено на домашней странице.

mapStateToProps - это функция, предоставляемая React Redux, чтобы мы могли напрямую отображать состояние на свойства нашего компонента, как следует из названия функции. mapDispatchToProps позволяет нам вызывать функцию в свойствах компонента с именем setContacts для отправки действия, как мы определили в actionCreators.js

Затем мы создаем файл с именем 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"];

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

Затем мы создаем компонент Google Map для отображения местоположения контакта. Создаем файл с именем MapComponent.js и добавляем:

import React from "react";
import { compose, withProps } from "recompose";
import { GOOGLE_API_KEY } from "./requests";
import {
  withGoogleMap,
  GoogleMap,
  Marker,
  withScriptjs,
} from "react-google-maps";
const MapComponent = compose(
  withProps({
    googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&v=3.exp&libraries=geometry,drawing,places`,
    loadingElement: <div style={{ height: `100%` }} />,
    containerElement: <div style={{ height: `400px` }} />,
    mapElement: <div style={{ height: `100%` }} />,
  }),
  withScriptjs,
  withGoogleMap
)(({ lat, lng }) => (
  <GoogleMap defaultZoom={8} defaultCenter={{ lat, lng }}>
    <Marker position={{ lat, lng }} />
  </GoogleMap>
));
export default MapComponent;

Поля в аргументе вызова функции withProps являются реквизитами, необходимыми для компонента GoogleMap, withScriptjs загружает файлы JavaScript карты Google из Google. lat и lng - это реквизиты, которые мы передаем в компонент для отображения местоположения по широте и долготе соответственно.

В 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 MapComponent from "./MapComponent";
import { connect } from "react-redux";
import { getContacts, deleteContact, getLatLng } from "./requests";
function HomePage() {
  const [openAddModal, setOpenAddModal] = useState(false);
  const [openEditModal, setOpenEditModal] = useState(false);
  const [openMapModal, setOpenMapModal] = useState(false);
  const [initialized, setInitialized] = useState(false);
  const [loc, setLoc] = useState({
    lat: 0,
    lng: 0,
  });
  const [selectedContact, setSelectedContact] = useState({});
  const [contacts, setContacts] = useState([]);
  const openModal = () => {
    setOpenAddModal(true);
  };
  const closeModal = () => {
    setOpenAddModal(false);
    setOpenEditModal(false);
    setOpenMapModal(false);
    getData();
  };
  const cancelAddModal = () => {
    setOpenAddModal(false);
  };
  const editContact = contact => {
    setSelectedContact(contact);
    setOpenEditModal(true);
  };
  const cancelEditModal = () => {
    setOpenEditModal(false);
  };
  const getData = async () => {
    const response = await getContacts();
    setContacts(response.data);
    setInitialized(true);
  };
  const deleteSelectedContact = async id => {
    await deleteContact(id);
    getData();
  };
  const openMap = async contact => {
    try {
      const address = `${contact.addressLineOne}, ${contact.addressLineTwo}, ${contact.city}, ${contact.country}`;
      const response = await getLatLng(address);
      const loc = response.data.results[0].geometry.location;
      setLoc(loc);
      setOpenMapModal(true);
    } catch (ex) {
      console.log(ex);
    }
  };
  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}
          />
        </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}
          />
        </Modal.Body>
      </Modal>
      <Modal show={openMapModal} onHide={closeModal}>
        <Modal.Header closeButton>
          <Modal.Title>Map</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <MapComponent
            lat={loc.lat}
            lng={loc.lng}
          />
        </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>Map</th>
            <th>Edit</th>
            <th>Delete</th>
          </tr>
        </thead>
        <tbody>
          {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={openMap.bind(this, c)}
                >
                  Map
                </Button>
              </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>
  );
}
const mapStateToProps = state => {
  return {
    contacts: state.contacts,
  };
};
export default connect(
  mapStateToProps,
  null
)(HomePage);

В нем есть таблица для отображения контактов и кнопки для добавления, редактирования и удаления контакта. Он получает данные один раз при первой загрузке с помощью вызова функции 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 { contactsReducer } from './reducers';
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'
const addressBookApp = combineReducers({
    contacts: contactsReducer,
})
const store = createStore(addressBookApp)
ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>
    , 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();

Мы объединили редукторы и создали магазин, а затем внедрили его в наше приложение с помощью компонента Provider, чтобы мы могли использовать его везде в приложении.

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

import { SET_CONTACTS } from './actions';
function contactsReducer(state = {}, action) {
    switch (action.type) {
        case SET_CONTACTS:
            state = JSON.parse(JSON.stringify(action.payload));
            return state;
        default:
            return state
    }
}
export { contactsReducer };

Это редуктор, в котором мы храним контакты, которые мы отправляем, вызывая опору, предоставляемую функцией mapDispatchToProps в наших компонентах.

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

const APIURL = "http://localhost:3000";
const MAPURL = "https://maps.googleapis.com/maps/api/geocode/json?address=";
const axios = require("axios");
export const GOOGLE_API_KEY = "your API key";
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}`);
export const getLatLng = address => {
  return axios.get(
    `${MAPURL}${encodeURIComponent(address)}&key=${GOOGLE_API_KEY}`
  );
};

Это функции, которые отправляют наши 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