Подпишитесь на мою рассылку сейчас по адресу https://jauyeung.net/subscribe/.
Подпишитесь на меня в Twitter по адресу https://twitter.com/AuMayeung
Особенностью React является то, что вы должны сами обрабатывать изменения входных значений. В противном случае пользователи не смогут увидеть, что они ввели, поскольку значение не установлено в состоянии.
Чтобы обновить входное значение и установить его в состояние нашего компонента, сначала мы должны добавить:
const [title, setTitle] = React.useState("");
чтобы создать функцию setTitle
, чтобы установить значение title
.
Затем мы добавили функцию-обработчик, чтобы получить значение из ввода и установить его:
const handleTitleChange = ev => setTitle(ev.target.value);
ev.target.value
содержит входное значение.
Затем, когда мы добавляем ввод, мы должны добавить его следующим образом:
<input type="text" name="title" placeholder="Title" value={title || ""} onChange={handleTitleChange} isInvalid={!title} />
Мы передаем функцию handleTitleChange
в опору onChange
, чтобы было установлено значение title
. Затем, как только он будет установлен, будет заполнено value
prop, и пользователи смогут увидеть введенное значение.
В этой статье мы создадим простое приложение-календарь, в котором пользователи могут перетаскивать диапазон дат и добавлять запись в календарь. Пользователи также могут щелкнуть существующую запись календаря и отредактировать ее. Существующие записи также можно удалить. Форма для добавления и редактирования записи календаря будет иметь средства выбора даты и времени для выбора даты и времени.
В React есть много виджетов календаря, которые мы можем добавить в наши приложения. Один из них - React Big Calendar. В нем много функций. У него есть календарь на месяц, неделю и день. Кроме того, вы можете легко перейти к сегодняшнему дню или к любым другим дням с помощью кнопок «Назад» и «Далее». Вы также можете перетащить диапазон дат в календаре, чтобы выбрать диапазон дат. При этом вы можете производить любые манипуляции с датами.
Мы сохраним данные на бэкэнде в файле JSON.
Мы будем использовать React для создания нашего приложения. Для начала запускаем:
npx create-react-app calendar-app
создать проект.
Далее нам нужно установить несколько пакетов. Мы будем использовать Axios для HTTP-запросов к нашей серверной части, Bootstrap для стилизации, MobX для простого управления состоянием, React Big Calendar для нашего компонента календаря, React Datepicker для выбора даты и времени в нашей форме и React Router для маршрутизации.
Для их установки запускаем:
npm i axios bootstrap mobx mobx-react moment react-big-calendar react-bootstrap react-datepicker react-router-dom
После установки всех пакетов мы можем приступить к написанию кода. Сначала мы заменяем существующий код в 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"; import "react-big-calendar/lib/css/react-big-calendar.css"; import "react-datepicker/dist/react-datepicker.css"; const history = createHistory(); function App({ calendarStore }) { return ( <div> <Router history={history}> <Navbar bg="primary" expand="lg" variant="dark"> <Navbar.Brand href="#home">Calendar 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} calendarStore={calendarStore} /> )} /> </Router> </div> ); } export default App;
Мы добавляем сюда верхнюю панель React Bootstrap со ссылкой на домашнюю страницу. Кроме того, мы добавляем сюда маршрут для домашней страницы с переданным MobX calendarStore
.
Кроме того, мы импортируем сюда стили для средства выбора даты и календаря, чтобы мы могли использовать их во всем приложении.
Затем в App.css
замените существующий код на:
.page { padding: 20px; } .form-control.react-datepicker-ignore-onclickoutside, .react-datepicker-wrapper { width: 465px !important; } .react-datepicker__current-month, .react-datepicker-time__header, .react-datepicker-year-header, .react-datepicker__day-name, .react-datepicker__day, [class^="react-datepicker__day--*"], .react-datepicker__time-list-item { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; }
чтобы добавить отступ на нашу страницу, измените ширину поля ввода даты и шрифт окна выбора даты.
Затем создайте файл с именем CalendarForm.js
в папке src
и добавьте:
import React from "react"; import Form from "react-bootstrap/Form"; import Col from "react-bootstrap/Col"; import DatePicker from "react-datepicker"; import Button from "react-bootstrap/Button"; import { addCalendar, editCalendar, getCalendar, deleteCalendar } from "./requests"; import { observer } from "mobx-react"; const buttonStyle = { marginRight: 10 }; function CalendarForm({ calendarStore, calendarEvent, onCancel, edit }) { const [start, setStart] = React.useState(null); const [end, setEnd] = React.useState(null); const [title, setTitle] = React.useState(""); const [id, setId] = React.useState(null); React.useEffect(() => { setTitle(calendarEvent.title); setStart(calendarEvent.start); setEnd(calendarEvent.end); setId(calendarEvent.id); }, [ calendarEvent.title, calendarEvent.start, calendarEvent.end, calendarEvent.id ]); const handleSubmit = async ev => { ev.preventDefault(); if (!title || !start || !end) { return; } if (+start > +end) { alert("Start date must be earlier than end date"); return; } const data = { id, title, start, end }; if (!edit) { await addCalendar(data); } else { await editCalendar(data); } const response = await getCalendar(); const evs = response.data.map(d => { return { ...d, start: new Date(d.start), end: new Date(d.end) }; }); calendarStore.setCalendarEvents(evs); onCancel(); }; const handleStartChange = date => setStart(date); const handleEndChange = date => setEnd(date); const handleTitleChange = ev => setTitle(ev.target.value); const deleteCalendarEvent = async () => { await deleteCalendar(calendarEvent.id); const response = await getCalendar(); const evs = response.data.map(d => { return { ...d, start: new Date(d.start), end: new Date(d.end) }; }); calendarStore.setCalendarEvents(evs); onCancel(); }; return ( <Form noValidate onSubmit={handleSubmit}> <Form.Row> <Form.Group as={Col} md="12" controlId="title"> <Form.Label>Title</Form.Label> <Form.Control type="text" name="title" placeholder="Title" value={title || ""} onChange={handleTitleChange} isInvalid={!title} /> <Form.Control.Feedback type="invalid">{!title}</Form.Control.Feedback> </Form.Group> </Form.Row> <Form.Row> <Form.Group as={Col} md="12" controlId="start"> <Form.Label>Start</Form.Label> <br /> <DatePicker showTimeSelect className="form-control" selected={start} onChange={handleStartChange} /> </Form.Group> </Form.Row> <Form.Row> <Form.Group as={Col} md="12" controlId="end"> <Form.Label>End</Form.Label> <br /> <DatePicker showTimeSelect className="form-control" selected={end} onChange={handleEndChange} /> </Form.Group> </Form.Row> <Button type="submit" style={buttonStyle}> Save </Button> <Button type="button" style={buttonStyle} onClick={deleteCalendarEvent}> Delete </Button> <Button type="button" onClick={onCancel}> Cancel </Button> </Form> ); } export default observer(CalendarForm);
Это форма для добавления и редактирования записей календаря. Мы добавляем сюда форму React Bootstrap, добавляя компонент Form
. Form.Control
также из той же библиотеки. Мы используем его для ввода текста title
.
Остальные 2 поля - это даты начала и окончания. Здесь мы используем React Datepicker, чтобы пользователи могли выбирать даты начала и окончания календарной записи. Кроме того, мы включаем средство выбора времени, чтобы пользователи могли выбирать время.
В каждом поле есть обработчики изменений для обновления значений в состоянии, чтобы пользователи могли видеть, что они ввели, и позволить им отправить данные позже. Обработчики изменений: handleStartChange
, handleEndChange
и handleTitleChange
. Мы устанавливаем состояния с помощью функций установки, генерируемых useState
хуками.
Мы используем обратный вызов useEffect
, чтобы установить для полей в опоре calendarEvent
состояния. Мы передаем все поля, которые хотим установить в массив во втором аргументе функции useEffect
, чтобы состояния обновлялись всякий раз, когда передается последнее значение свойства calendarEvent
.
В функции handleSubmit
, которая вызывается при нажатии кнопки Сохранить в форме. мы должны вызвать ev.preventDefault
, чтобы мы могли использовать Ajax для отправки данных формы.
Если проверка данных проходит, мы отправляем данные, получаем самые свежие и сохраняем их в нашем calendarStore
магазине MobX.
Мы переносим observer
за пределы CalendarForm
компонента, чтобы всегда получать последние значения из calendarStore
.
Далее мы создаем нашу домашнюю страницу. Создайте файл HomePage.js
в папке src
и добавьте:
import React from "react"; import { Calendar, momentLocalizer } from "react-big-calendar"; import moment from "moment"; import Modal from "react-bootstrap/Modal"; import CalendarForm from "./CalendarForm"; import { observer } from "mobx-react"; import { getCalendar } from "./requests"; const localizer = momentLocalizer(moment); function HomePage({ calendarStore }) { const [showAddModal, setShowAddModal] = React.useState(false); const [showEditModal, setShowEditModal] = React.useState(false); const [calendarEvent, setCalendarEvent] = React.useState({}); const [initialized, setInitialized] = React.useState(false); const hideModals = () => { setShowAddModal(false); setShowEditModal(false); }; const getCalendarEvents = async () => { const response = await getCalendar(); const evs = response.data.map(d => { return { ...d, start: new Date(d.start), end: new Date(d.end) }; }); calendarStore.setCalendarEvents(evs); setInitialized(true); }; const handleSelect = (event, e) => { const { start, end } = event; const data = { title: "", start, end, allDay: false }; setShowAddModal(true); setShowEditModal(false); setCalendarEvent(data); }; const handleSelectEvent = (event, e) => { setShowAddModal(false); setShowEditModal(true); let { id, title, start, end, allDay } = event; start = new Date(start); end = new Date(end); const data = { id, title, start, end, allDay }; setCalendarEvent(data); }; React.useEffect(() => { if (!initialized) { getCalendarEvents(); } }); return ( <div className="page"> <Modal show={showAddModal} onHide={hideModals}> <Modal.Header closeButton> <Modal.Title>Add Calendar Event</Modal.Title> </Modal.Header> <Modal.Body> <CalendarForm calendarStore={calendarStore} calendarEvent={calendarEvent} onCancel={hideModals.bind(this)} edit={false} /> </Modal.Body> </Modal> <Modal show={showEditModal} onHide={hideModals}> <Modal.Header closeButton> <Modal.Title>Edit Calendar Event</Modal.Title> </Modal.Header> <Modal.Body> <CalendarForm calendarStore={calendarStore} calendarEvent={calendarEvent} onCancel={hideModals.bind(this)} edit={true} /> </Modal.Body> </Modal> <Calendar localizer={localizer} events={calendarStore.calendarEvents} startAccessor="start" endAccessor="end" selectable={true} style={{ height: "70vh" }} onSelectSlot={handleSelect} onSelectEvent={handleSelectEvent} /> </div> ); } export default observer(HomePage);
Мы получаем календарные записи и заполняем их здесь. Записи извлекаются из серверной части и затем сохраняются в магазине. В обратном вызове useEffect
мы устанавливаем получение элементов при загрузке страницы. Мы делаем это только тогда, когда initialized
имеет значение false, поэтому мы не будем перезагружать данные каждый раз при отображении страницы.
Чтобы открыть модальное окно для добавления записей календаря, мы устанавливаем свойство onSelectSlot
с нашим обработчиком, чтобы мы могли вызывать setShowAddModal
и setCalendarEvent
, чтобы открыть модальное окно и установить даты перед открытием модального окна добавления события календаря.
Точно так же мы устанавливаем модальное окно onSelectEvent
с функцией обработчика handleSelectEvent
, чтобы открыть модальное окно редактирования и установить данные календарного события существующей записи.
В каждом модальном окне есть компонент CalendarForm
. Мы передаем функцию закрытия модальных окон в форму, чтобы мы могли закрыть их из формы. Кроме того, мы передаем calendarStore
и calendarEvent
, чтобы ими можно было управлять в CalendarForm
.
Мы переносим observer
за пределы CalendarForm
компонента, чтобы всегда получать последние значения из calendarStore
.
Затем в index.js
мы заменяем существующий код на:
import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; import { CalendarStore } from "./store"; const calendarStore = new CalendarStore(); ReactDOM.render( <App calendarStore={calendarStore} />, 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();
так что мы можем передать MobX calendarStore
в корневой App
компонент.
Затем создайте файл requests.js
в папке src
и добавьте:
const APIURL = "https://localhost:3000"; const axios = require("axios"); export const getCalendar = () => axios.get(`${APIURL}/calendar`); export const addCalendar = data => axios.post(`${APIURL}/calendar`, data); export const editCalendar = data => axios.put(`${APIURL}/calendar/${data.id}`, data); export const deleteCalendar = id => axios.delete(`${APIURL}/calendar/${id}`);
Это функции для выполнения HTTP-вызовов для управления записями календаря.
Затем создайтеstore.js
в папке src
и добавьте:
import { observable, action, decorate } from "mobx"; class CalendarStore { calendarEvents = []; setCalendarEvents(calendarEvents) { this.calendarEvents = calendarEvents; } } CalendarStore = decorate(CalendarStore, { calendarEvents: observable, setCalendarEvents: action }); export { CalendarStore };
чтобы сохранить товары в магазине для доступа всех наших компонентов.
Затем в 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" /> <!-- 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/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- 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>Calendar 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>
чтобы добавить CSS Bootstrap и переименовать заголовок.
Теперь вся тяжелая работа сделана. Все, что нам нужно сделать, это использовать пакет JSON Server NPM, расположенный по адресу https://github.com/typicode/json-server, для нашей серверной части.
Установите его, запустив:
npm i -g json-server
Затем запустите его, запустив:
json-server --watch db.json
В db.json
замените существующее содержимое на:
{ "calendar": [] }
Затем мы запускаем наше приложение, запустив npm start
в папке проекта нашего приложения, и когда программа попросит вас запустить его через другой порт, выберите «Да».
После этого вы должны увидеть: