Создание проекта на основе SaaS для журнала калорий с использованием стека MERN 🔥
Эта статья в блоге концентрируется на наиболее важных задачах и идеях, чтобы помочь вам лучше понять и создать стековые приложения MERN с нуля. Он предназначен для людей, которые действительно интересуются стеком MERN и хотят сосредоточиться на том, что им действительно нужно знать.
Итак, что такое стек MERN?
Стек MERN - это популярный стек технологий для создания современных одностраничных приложений, также известных сокращенно как SPA. MongoDB, Express, React и Node.js - это аббревиатуры для стека «MERN». MERN - это вариант очень популярного стека MEAN (MongoDB, Express, Angular, Node), в котором React заменяет Angular в качестве внешнего интерфейса. MEVN (MongoDB, Express, Vue, Node), который использует Vue в качестве интерфейса пользователя, является еще одним очень популярным вариантом. Этот технический стек внешних интерфейсов помогает создавать одностраничные приложения (SPA), которые помогают избежать перезагрузки всей страницы и получать только соответствующие фрагменты информации страницы с сервера и отображать свежие и недавно обновленные материалы.
В этой статье блога мы создадим приложение для отслеживания калорий с полным стеком, которое пользователи смогут использовать для отслеживания пищевых привычек пользователей и смогут отслеживать их полное количество калорий, используя только абсолютную мощность стека MERN. Это руководство в блоге должно помочь вам понять основы, а также продвинутые концепции и операции стековой технологии MERN.
Настройка нашей структуры папок
Создайте клиент и сервер с двумя папками в каталоге проекта, затем откройте его в Visual Studio Code или любом редакторе кода по вашему выбору.
Теперь мы настроим наш бэкэнд с npm и установим необходимые пакеты, затем настроим базу данных MongoDB, настроим сервер с помощью Node и Express, установим схему базы данных для описания нашего приложения для отслеживания калорий и настроим маршруты API для создания, чтения, обновления и удаления данных и информации из базы данных. Итак, используя командную строку, перейдите в каталог вашего сервера и запустите приведенный ниже код.
npm init -y
Настройка и обновление нашего файла package.json
Выполните следующие команды в терминале, чтобы установить зависимости.
npm install cors dotenv express mongoose nodemon body-parser
После установки зависимостей файл package.json должен выглядеть так.
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"start": "nodemon app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"mongoose": "^6.0.13",
"nodemon": "^2.0.15"
}
}
Также не забывайте обновить скрипты.
Теперь перейдите в каталог вашего сервера и создайте там файл app.js.
Структура ваших папок и файла должна напоминать эту.
Настройка app.js
- Импортировать модуль express.
- Импортировать модуль mongoose
- Импортировать и настроить модуль dotenv
- Импортировать модуль CORS
- Используйте
express()
, чтобы запустить наше приложение.
//app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const app = express();
В этом экземпляре приложения мы теперь можем использовать все различные методы. Начнем с базовой настройки. Не забудьте также настроить порт.
// app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const app = express();
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
Настройка облачного кластера MongoDB
MongoDB - это база данных NoSQL, которая хранит данные в JSON-подобных документах с дополнительными схемами. Версии до 16 октября 2018 г. выпускаются по лицензии AGPL. Все версии, выпущенные после 16 октября 2018 г., включая исправления ошибок для предыдущих версий, распространяются по лицензии SSPL v1.
Официальный сайт MongoDB
Войдите в MongoDB
Создать проект
Добавление участников
Создание базы данных
Создание кластера
Выбор поставщика облачных услуг
Быстрый старт по безопасности
Закончите и закройте создание кластера и подождите, пока кластер будет построен, прежде чем продолжить (обычно это занимает около 5-10 минут)
Перейдите на вкладку доступа к сети и выберите «Добавить IP-адрес».
Теперь выберите «Выбрать способ подключения».
Подключите свое приложение, щелкнув по нему, и, наконец, выберите правильный драйвер и версию.
В базе данных создайте пользователя. Вам понадобятся имя пользователя и пароль для URI MongoDB и, наконец, создайте пользователя базы данных.
Облачный атлас запущен
Теперь внутри app.js создайте новую переменную и назовите ее DATABASE_CONNECTION. Внутри него создайте строку и просто вставьте скопированный URL-адрес подключения к базе данных mongo или просто вставьте ссылку для переменных среды. Теперь внутри ссылки URL-адреса облачного атласа Mongo Sb введите свое имя пользователя и пароль, не забудьте удалить все скобки и ввести свои собственные учетные данные. Второе, что нам нужно, это ПОРТ, поэтому просто введите номер порта, пока 6000, и, наконец, мы будем использовать mongoose для подключения к нашей базе данных, поэтому введите mongoose.connect()
- функция с двумя разными параметрами. Первым будет DATABASE_CONNECTION, а вторым - объект с двумя разными параметрами. Первым является useNewUrlParser
, для которого мы установим значение true, а вторым - useUnifiedTopology
, для которого мы также установим значение true. Эти объекты не требуются, но мы увидим некоторые ошибки или предупреждения на нашей консоли. После этого давайте объединим .then()
и .catch()
, потому что это вернет обещание, поэтому внутри .then()
вызовет приложение и вызовет listen, который имеет два параметра, первый из которых - PORT, а второй в том числе функция обратного вызова, которая будет выполнена, если наше приложение будет успешно подключено, и, наконец, если подключение к базе данных не удалось, мы просто запишем наше сообщение об ошибке в консольный журнал.
// app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const app = express();
// app config
app.use(cors());
app.use(express.json());
// port and DB config
const DATABASE_CONNECTION = process.env.DATABASE_URL;
const PORT = process.env.PORT || 5000;
// mongoose connection
mongoose
.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() =>
app.listen(PORT, () =>
console.log(`Server is running at : http://localhost:${PORT}`)
)
)
.catch((error) => console.error(error));
Вставьте mongodb + srv в файл .env.
PORT=6000
DATABASE_URL=mongodb+srv://pramit:<password>@cluster0.uauqv.mongodb.net/myFirstDatabase?retryWrites=true&w=majority
Вот и все, что нужно сделать; мы успешно подключили наш сервер к базе данных.
Теперь, когда мы успешно подключились к нашей базе данных, давайте приступим к построению маршрутов нашего серверного приложения. Для этого нам нужно создать новую папку с именем routes в каталоге сервера. Мы создадим файл с именем calorie.routes.js
в папке маршрутов.
Вот как должны быть организованы ваши папки.
Давайте начнем с импорта калорий и пользовательских маршрутов в ваш файл app.js. Теперь мы можем связать калорийность и пользователя с нашим приложением с помощью промежуточного программного обеспечения Express. Наконец, ваш файл app.js должен выглядеть следующим образом.
//app.js
const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
require("dotenv").config();
const app = express();
// app config
app.use(cors());
app.use(express.json());
// port and DB config
const DATABASE_CONNECTION = process.env.DATABASE_URL;
const PORT = process.env.PORT || 5000;
// mongoose connection
mongoose
.connect(DATABASE_CONNECTION, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() =>
app.listen(PORT, () =>
console.log(`Server is running at : http://localhost:${PORT}`)
)
)
.catch((error) => console.error(error));
// routers
const calorie = require("./routes/calorie.routes.js");
const users = require("./routes/users.routes.js");
app.use("/calorie", calorie);
app.use("/users", users);
Мы собираемся добавить все маршруты, а также их контроллеры внутри calorie.routes.js и user.routes.js, поэтому сначала мы должны импортировать express, а также настроить наш маршрутизатор. Но сначала давайте создадим модель для наших пользователей и калорий. Итак, создайте папку с именем models и внутри этой папки создайте два файла с именами calorie.model.js и users.model.js и вставьте следующий код в каждый из их.
Теперь ваша структура папок должна выглядеть примерно так
//models/calorie.model.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const calorieSchema = new Schema({
username: {
type: String,
required: true
},
description: {
type: String,
required: true
},
calories: {
type: Number,
required: true
},
date: {
type: Date,
required: true
},
}, {
timestamps: true,
});
const Calorie = mongoose.model("CalorieJournal", calorieSchema);
module.exports = Calorie;
а также
//models/users.model.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 2,
},
}, {
timestamps: true,
});
const User = mongoose.model("User", userSchema);
module.exports = User;
Теперь мы можем начать добавлять к нему наши маршруты.
//routes/calorie.routes.js
const router = require("express").Router();
let Calorie = require("../models/calorie.model.js");
router.route("/").get((req, res) => {
Calorie.find()
.then((meals) => res.json(meals))
.catch((err) => res.status(400).json("Error: " + err));
});
router.route("/add").post((req, res) => {
const username = req.body.username;
const description = req.body.description;
const calories = Number(req.body.calories);
const date = Date.parse(req.body.date);
const addCalorie = new Calorie({
username,
description,
calories,
date,
});
addCalorie
.save()
.then(() => res.json("Calories Added Successfully"))
.catch((err) => res.status(400).json("Error: " + err));
});
Получение всей информации о калориях.
Удаление информации об отдельной калории.
Обновление информации об одной калории.
Наконец, экспортируем роутер
Ваш файл calorie.route.js должен выглядеть так.
//models/calorie.model.js
const router = require("express").Router();
let Calorie = require("../models/calorie.model.js");
router.route("/").get((req, res) => {
Calorie.find()
.then((meals) => res.json(meals))
.catch((err) => res.status(400).json("Error: " + err));
});
router.route("/add").post((req, res) => {
const username = req.body.username;
const description = req.body.description;
const calories = Number(req.body.calories);
const date = Date.parse(req.body.date);
const addCalorie = new Calorie({
username,
description,
calories,
date,
});
addCalorie
.save()
.then(() => res.json("Calories Added Successfully"))
.catch((err) => res.status(400).json("Error: " + err));
});
router.route("/:id").get((req, res) => {
Calorie.findById(req.params.id)
.then((calories) => res.json(calories))
.catch((err) => res.status(400).json("Error: " + err));
});
router.route("/:id").delete((req, res) => {
Calorie.findByIdAndDelete(req.params.id)
.then(() => res.json("Calories is deleted Successfully"))
.catch((err) => res.status(400).json("Error: " + err));
});
router.route("/update/:id").post((req, res) => {
Calorie.findById(req.params.id)
.then((calories) => {
calories.username = req.body.username;
calories.description = req.body.description;
calories.calories = Number(req.body.calories);
calories.date = Date.parse(req.body.date);
calories
.save()
.then(() => res.json("Calorie Updated Successfully"))
.catch((err) => res.status(400).json("Err: " + err));
})
.catch((err) => res.status(400).json("Err: " + err));
});
module.exports = router;
Теперь обновим маршруты пользователей.
//routes/user.routes.js
const router = require("express").Router();
let User = require("../models/users.model.js");
Получить информацию о пользователе
Добавление информации о пользователе
Наконец, экспортируем роутер
Ваш файл users.route.js должен выглядеть так.
//routes/user.routes.js
const router = require("express").Router();
let User = require("../models/users.model.js");
// get user
router.route("/").get((req, res) => {
User.find()
.then((users) => res.json(users))
.catch((err) => res.status(400).json("Error: " + err));
});
// add user
router.route("/add").post((req, res) => {
const username = req.body.username;
const newUser = new User({
username
});
newUser
.save()
.then(() => res.json("User added Successfully"))
.catch((err) => res.status(400).json("Error: " + err));
});
module.exports = router;
После перезапуска сервера вы должны увидеть что-то вроде этого:
Настройка нашего Frontend
Мы начнем с использования create-react-app для настройки нашего интерфейса. Мы создадим пользовательский интерфейс и его функции с нуля. Сразу приступим к работе над нашим приложением.
Настройка React на загрузку приложения с использованием CRA
Начнем с внешнего интерфейса и создадим его с помощью react. Первое, что вам нужно сделать, это установить Node.js, если он еще не установлен на вашем компьютере. Итак, зайдите на официальный сайт Node.js и загрузите последнюю версию. Node js требуется для использования диспетчера пакетов node, обычно известного как NPM. Теперь откройте папку клиента в предпочитаемом вами редакторе кода. Я буду использовать VScode. Затем откройте интегрированный терминал и введите npx create-response-app. Эта команда создаст клиентское приложение в текущем каталоге, используя имя client.
Обычно мы используем npm для добавления пакетов в проект, но в этом случае мы будем использовать npx, средство запуска пакетов, которое загрузит и настроит все за нас, чтобы мы могли сразу приступить к работе с отличным шаблоном. Пришло время запустить наш сервер разработки, поэтому запустите npm start, и браузер мгновенно откроет приложение response-app.
React очистка шаблонных файлов
Мы должны сначала привести наши проекты в порядок, удалив некоторые файлы, предоставленные приложением create-react-app, прежде чем мы сможем начать их создавать. После того, как вы очистили свои файлы и папку, они должны выглядеть так.
Добавление и установка некоторых пакетов
Нам нужно будет установить несколько сторонних пакетов для этого проекта. поэтому скопируйте и вставьте следующую команду в свой терминал
npm install bootstrap react-chartjs-2 chart.js axios react-datepicker react-router-dom
После установки всех этих пакетов ваш клиентский файл packge.json должен выглядеть так:
Давайте создадим семь отдельных папок / компонентов внутри папки компонентов после того, как мы установили все зависимости нашего проекта, и назовем его как Navbar, CalorieChart, UserChart, AddFood, AddUser, EditFood и DisplayFoodList.
Структура ваших файлов и папок должна выглядеть примерно так, как только вы добавите все свои компоненты.
Теперь перейдите в файл app.js и импортируйте маршрутизаторы из react-router-dom и стилей, а также файл начальной загрузки css, а также все компоненты и внесите необходимые изменения в код, как показано ниже.
// app.js
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import Navbar from "./components/Navbar";
import DisplayFoodList from "./components/DisplayFoodList";
import EditFood from "./components/EditFood";
import AddFood from "./components/AddFood";
import AddUser from "./components/AddUser";
function App() {
return (
<>
<Router>
<Navbar />
<br />
<Routes>
<Route path="/" exact element={<DisplayFoodList />} />
<Route path="/edit/:id" element={<EditFood />} />
<Route path="/create" element={<AddFood />} />
<Route path="/user" element={<AddUser />} />
</Routes>
</Router>
</>
);
}
export default App;
затем перейдите к компоненту панели навигации и вставьте в него приведенный ниже код.
//components/Navbar/Navbar.js
import React from "react";
import { Link } from "react-router-dom";
const Navbar = () => {
return (
<nav
className="navbar navbar-expand-lg navbar-light static-top mb-0 shadow"
style={{ backgroundColor: "#8661d1" }}
>
<div className="container">
<Link to="/">
<img
alt="Calorie Journal Logo"
src="https://user-images.githubusercontent.com/37651620/142762093-45207811-0c6e-4b62-9cb2-8d0009efb4ea.png"
width="70"
height="70"
className="d-inline-block align-top"
/>
</Link>
<Link
className="navbar-brand"
to="/"
className="navbar-brand"
style={{
color: "white",
fontSize: "1.5rem",
marginRight: "15rem",
marginLeft: "30rem",
}}
>
<img
src="https://user-images.githubusercontent.com/37651620/142764762-fef8f764-4cd5-44c6-8b9a-cffcfab2ccf8.png"
alt="calorie journal"
style={{ height: "100px" }}
/>
</Link>
<div className="collapse navbar-collapse">
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link
className="nav-link"
to="/"
className="nav-link"
style={{
fontSize: "0.2rem",
color: "white",
}}
>
<button type="button" className="btn btn-info">
Calorie Info
</button>
</Link>
</li>
<li className="nav-item active">
<Link
className="nav-link"
to="/create"
className="nav-link"
style={{
fontSize: "0.2rem",
color: "white",
}}
>
<button type="button" className="btn btn-info">
➕ Add food
</button>
</Link>
</li>
<li className="nav-item">
<Link
className="nav-link"
to="/user"
className="nav-link"
style={{
fontSize: "0.2rem",
color: "white",
}}
>
<button type="button" className="btn btn-warning">
➕ Add User
</button>
</Link>
</li>
</ul>
</div>
</div>
</nav>
);
};
export default Navbar;
Пришло время определить наш компонент AddFood, теперь, когда мы успешно ввели компонент панели навигации в наше приложение.
import React,{useState,useEffect,useRef} from "react";
import axios from "axios";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
В компоненте AddFood добавьте перехватчик useState()
, который позволит нам включить состояние в наш функциональный компонент. useState()
не работает со значениями объекта, в отличие от состояния в компонентах класса. При необходимости мы можем использовать примитивы напрямую для создания нескольких перехватчиков React для нескольких переменных.
const [state, setState] = useState(initialState);
Хуки всегда должны быть объявлены в начале функции в React. Это также помогает поддерживать состояние компонента, а также сохранять его между отрисовками.
Что такое useRef()?
Этот хук просто возвращает изменяемый объект ref с переданным аргументом в качестве свойства its.current(initialValue). Возвращенный объект будет храниться в течение всего срока службы компонента.
const refContainer = useRef(initialValue);
Давайте вернемся к коду и реализуем функциональность useRef.
const userInputRef = useRef("userInput");
Давайте посмотрим на хук useEffect(). Вы уведомляете React о том, что вашему компоненту необходимо что-то выполнить после рендеринга, с помощью этого хука. После завершения модификаций DOM React запомнит предоставленную вами функцию (которую мы будем называть нашим «эффектом»). Для этого мы устанавливаем заголовок документа, но в качестве альтернативы мы могли бы выполнить выборку данных или вызвать другой обязательный API. Использование useEffect() внутри компонента позволяет нам напрямую обращаться к переменной состояния счетчика (или любым реквизитам) из эффекта. Он уже находится в области видимости функции, поэтому нам не нужен новый API для его чтения. Хуки используют закрытие JavaScript, а не предоставляют API, специфичные для React, там, где это уже есть в JavaScript. Ловушка useEffect() сравнима с методами жизненного цикла для компонентов класса, с которыми мы знакомы. Он выполняется после каждого рендеринга компонента, включая начальный рендеринг. В результате, componentDidMount, componentDidUpdate и componentWillUnmount можно рассматривать как один компонент. Мы можем передать зависимости эффекту, чтобы определить поведение, когда эффект должен выполняться (только при начальном рендеринге или только при изменении определенной переменной состояния). У этой ловушки также есть опция очистки, которая позволяет очистить ресурсы до того, как компонент будет уничтожен. useEffect(didUpdate) - это основной синтаксис эффекта. Или только при изменении конкретной переменной состояния).
Давайте создадим функцию, которая извлекает всю информацию о пользователе.
useEffect(() => {
axios
.get("http://localhost:5000/users/")
.then((response) => {
if (response.data.length > 0) {
setUsers(response.data.map((user) => user.username));
setUsername(response.data[0].username);
}
})
.catch((error) => {
console.log(error);
});
}, []);
Теперь создайте пять функций или обработчиков и назовите их как handleUsername, handlDescription, handleCalories, handleDate и handleSubmit.
function handleUsername(e) {
setUsername(e.target.value);
}
function handleDescription(e) {
setDescription(e.target.value);
}
function handleCalories(e) {
setCalories(e.target.value);
}
function handleDate(date) {
setDate(date);
}
function handleSubmit(e) {
e.preventDefault();
const meal = {
username,
description,
calories,
date,
};
console.log(meal);
axios
.post("http://localhost:5000/calorie/add", meal)
.then((res) => console.log(res.data));
window.location = "/";
}
Наконец, ваш компонент AddFood должен выглядеть примерно так
//components/AddFood
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
const AddFood = () => {
const [username, setUsername] = useState("");
const [description, setDescription] = useState("");
const [calories, setCalories] = useState("");
const [date, setDate] = useState(new Date());
const [users, setUsers] = useState([]);
const userInputRef = useRef("userInput");
useEffect(() => {
axios
.get("http://localhost:5000/users/")
.then((response) => {
if (response.data.length > 0) {
setUsers(response.data.map((user) => user.username));
setUsername(response.data[0].username);
}
})
.catch((error) => {
console.log(error);
});
}, []);
function handleUsername(e) {
setUsername(e.target.value);
}
function handleDescription(e) {
setDescription(e.target.value);
}
function handleCalories(e) {
setCalories(e.target.value);
}
function handleDate(date) {
setDate(date);
}
function handleSubmit(e) {
e.preventDefault();
const meal = {
username,
description,
calories,
date,
};
console.log(meal);
axios
.post("http://localhost:5000/calorie/add", meal)
.then((res) => console.log(res.data));
window.location = "/";
}
return (
<>
<div className="container">
<div className="card border-0 shadow my-4">
<div className="card-body p-3"></div>
<div>
<h3 style={{ textAlign: "center" }}>
<img
src="https://user-images.githubusercontent.com/37651620/142764215-78f5b75f-4871-451e-9a4d-dd77cc667fc5.png"
alt="Food"
style={{ height: "150px" }}
/>{" "}
</h3>
<form onSubmit={handleSubmit}>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "15px",
marginRight: "20px",
}}
>
<label>👤 User name: </label>
<select
ref={userInputRef}
required
className="form-control"
value={username}
onChange={handleUsername}
>
{users.map(function (user) {
return (
<option key={user} value={user}>
{user}
</option>
);
})}
</select>
</div>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "25px",
marginRight: "20px",
}}
>
<label>🥡 Food Info: </label>
<input
type="text"
required
className="form-control"
value={description}
onChange={handleDescription}
/>
</div>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "15px",
marginRight: "20px",
}}
>
<label>🔥 Calories: </label>
<input
type="text"
className="form-control"
value={calories}
onChange={handleCalories}
/>
</div>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "15px",
marginRight: "20px",
}}
>
<div style={{ textAlign: "center", cursor: "pointer" }}>
<label>Date: </label>
<div>
<DatePicker selected={date} onChange={handleDate} />
</div>
</div>
</div>
<div className="form-group" style={{ textAlign: "center" }}>
<input
type="submit"
value="Add Meal"
className="btn"
style={{
color: "white",
backgroundColor: "#8661d1",
marginBottom: "25px",
}}
/>
</div>
</form>
</div>
</div>
</div>
</>
);
};
export default AddFood;
Теперь, когда мы успешно ввели компонент AddFood в наше приложение. Скопируйте следующий код и вставьте его в компонент AddUser.
//components/AddUser
import React, { useState } from "react";
import axios from "axios";
const AddUser = () => {
const [username, setUsername] = useState("");
function handleUsername(e) {
setUsername(e.target.value);
}
function handleSubmit(e) {
e.preventDefault();
const user = {
username,
};
console.log(user);
axios
.post("http://localhost:5000/users/add", user)
.then((res) => console.log(res.data));
setUsername("");
}
return (
<>
<div class="container">
<div class="card border-0 shadow my-4">
<div class="card-body p-3"></div>
<div>
<h3 style={{ textAlign: "center", marginBottom: "15px" }}>
<img
src="https://user-images.githubusercontent.com/37651620/142767072-ff777861-7ee9-4355-b48e-a624e8de085b.png"
alt="Logo"
style={{ height: "150px" }}
/>
</h3>
<form onSubmit={handleSubmit}>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "15px",
marginRight: "20px",
}}
>
<label>👤 User name:</label>
<input
type="text"
required
className="form-control"
value={username}
onChange={handleUsername}
/>
</div>
<div
className="form-group"
style={{
textAlign: "center",
}}
>
<input
type="submit"
value="Create User"
className="btn "
style={{
color: "white",
marginBottom: "25px",
backgroundColor: "#8661d1",
}}
/>
</div>
</form>
</div>
</div>
</div>
</>
);
};
export default AddUser;
Теперь, когда мы завершили компонент AddUser, пришло время создать функцию, которая позволяет нам изменять наши данные, поэтому мы создадим компонент EditFood.
//components/EditFood
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
const EditFood = (props) => {
const [username, setUsername] = useState("");
const [description, setDescription] = useState("");
const [calories, setCalories] = useState("");
const [date, setDate] = useState(new Date());
const [users, setUsers] = useState([]);
const userInputRef = useRef("userInput");
useEffect(() => {
axios
.get("http://localhost:5000/calorie/" + props.match.params.id)
.then((response) => {
setUsername(response.data.username);
setDescription(response.data.description);
setCalories(response.data.calories);
setDate(new Date(response.data.date));
})
.catch((error) => {
console.log(error);
});
axios
.get("http://localhost:5000/users/")
.then((response) => {
if (response.data.length > 0) {
setUsers(response.data.map((user) => user.username));
setUsername(response.data[0].username);
}
})
.catch((error) => {
console.log(error);
});
}, [props.match.params.id]);
function handleUsername(e) {
setUsername(e.target.value);
}
function handleDescription(e) {
setDescription(e.target.value);
}
function handleCalories(e) {
setCalories(e.target.value);
}
function handleDate(date) {
setDate(date);
}
function handleSubmit(e) {
e.preventDefault();
const food = {
username,
description,
calories,
date,
};
console.log(food);
axios
.post("http://localhost:5000/calorie/update", food)
.then((res) => console.log(res.data));
window.location = "/";
}
return (
<>
<div className="container">
<div className="card border-0 shadow my-4">
<div className="card-body p-3"></div>
<div>
<h3 style={{ textAlign: "center" }}>
<img
src="https://user-images.githubusercontent.com/37651620/142764215-78f5b75f-4871-451e-9a4d-dd77cc667fc5.png"
alt="Food"
style={{ height: "150px" }}
/>{" "}
</h3>
<form onSubmit={handleSubmit}>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "15px",
marginRight: "20px",
}}
>
<label>👤 User name: </label>
<select
ref={userInputRef}
required
className="form-control"
value={username}
onChange={handleUsername}
>
{users.map(function (user) {
return (
<option key={user} value={user}>
{user}
</option>
);
})}
</select>
</div>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "25px",
marginRight: "20px",
}}
>
<label>🥡 Food Info: </label>
<input
type="text"
required
className="form-control"
value={description}
onChange={handleDescription}
/>
</div>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "15px",
marginRight: "20px",
}}
>
<label>🔥 Calories: </label>
<input
type="text"
className="form-control"
value={calories}
onChange={handleCalories}
/>
</div>
<div
className="form-group"
style={{
marginLeft: "20px",
marginBottom: "15px",
marginRight: "20px",
}}
>
<div style={{ textAlign: "center", cursor: "pointer" }}>
<label>Date: </label>
<div>
<DatePicker selected={date} onChange={handleDate} />
</div>
</div>
</div>
<div className="form-group" style={{ textAlign: "center" }}>
<input
type="submit"
value="Add Meal"
className="btn"
style={{
color: "white",
backgroundColor: "#8661d1",
marginBottom: "25px",
}}
/>
</div>
</form>
</div>
</div>
</div>
</>
);
};
export default EditFood;
Давайте сконцентрируемся на визуализации полученных данных в виде диаграмм с помощью библиотеки react-chartjs-2, прежде чем мы начнем получать и отображать всю информацию на нашей домашней странице.
Итак, давайте создадим два отдельных компонента, один для гистограммы, а другой для круговой диаграммы, и как только вы это сделаете, скопируйте следующий код в каждый компонент.
//components/UserChart.js
import React, { useEffect, useState } from "react";
import { Pie } from "react-chartjs-2";
import axios from "axios";
const Delayed = ({ children, waitBeforeShow = 4500 }) => {
const [isShown, setIsShown] = useState(false);
useEffect(() => {
setTimeout(() => {
setIsShown(true);
}, waitBeforeShow);
}, [waitBeforeShow]);
return isShown ? children : null;
};
const UserChart = () => {
const [chartData, setChartData] = useState({});
async function getData() {
let username = [];
let calories = [];
await axios
.get("http://localhost:5000/calorie/")
.then((res) => {
console.log(res);
for (const dataObj of res.data) {
username.push(dataObj.username);
calories.push(parseInt(dataObj.calories));
console.log(username, calories);
}
setChartData({
labels: username,
datasets: [
{
label: "Calories",
data: calories,
backgroundColor: [
"#f42f42",
"#5ab950",
"#fe812a",
"#ffc748",
"#6b71c7",
"#8661d1",
"#8a2cba",
],
borderColor: [
"#f42f42",
"#5ab950",
"#fe812a",
"#ffc748",
"#6b71c7",
"#8661d1",
"#8a2cba",
],
borderWidth: 2,
},
],
});
})
.catch((err) => {
console.log(err);
});
console.log(username, calories);
}
useEffect(() => {
getData();
}, []);
return (
<div className="App">
<div>
<h5
style={{
fontSize: "20",
textAlign: "center",
marginTop: "1em",
marginBottom: "1em",
}}
>
Calorie per user
</h5>
<Delayed>
<Pie
data={chartData}
options={{
title: "{"
text: "Calorie per User",
fontSize: 10,
fontColor: "#212529",
},
maintainAspectRatio: true,
}}
/>
</Delayed>
</div>
</div>
);
};
export default UserChart;
//components/CalorieChart
import React, { useEffect, useState } from "react";
import { Bar } from "react-chartjs-2";
import axios from "axios";
const Delayed = ({ children, waitBeforeShow = 4500 }) => {
const [isShown, setIsShown] = useState(false);
useEffect(() => {
setTimeout(() => {
setIsShown(true);
}, waitBeforeShow);
}, [waitBeforeShow]);
return isShown ? children : null;
};
const CalorieChart = () => {
const [chartData, setChartData] = useState({});
async function getData() {
let foodCal = [];
let caloriesCal = [];
await axios
.get("http://localhost:5000/calorie/")
.then((res) => {
console.log(res);
for (let dataObj of res.data) {
foodCal.push(dataObj.description);
caloriesCal.push(parseInt(dataObj.caloriesCal));
console.log("foodCal, caloriesCal", foodCal, caloriesCal);
}
setChartData({
labels: foodCal,
datasets: [
{
label: "Cal",
data: caloriesCal,
backgroundColor: [
"#f42f42",
"#5ab950",
"#fe812a",
"#ffc748",
"#6b71c7",
"#8661d1",
"#8a2cba",
],
},
],
});
})
.catch((err) => {
console.log(err);
});
}
useEffect(() => {
getData();
}, []);
return (
<div className="App">
<h4>Food Analytics</h4>
<h5
style={{
fontSize: "20",
textAlign: "center",
marginBottom: "1em",
}}
>
Calorie Intake per each Food
</h5>
<div>
<Delayed>
<Bar
data={chartData}
options={{
responsive: true,
title: "{"
text: "Calorie Per Food ",
fontSize: 20,
fontColor: "#212529",
},
scales: {
yAxes: [
{
ticks: {
autoSkip: true,
maxTicksLimit: 10,
beginAtZero: true,
},
gridLines: {
// display: true,
},
},
],
xAxes: [
{
gridLines: {
display: true,
},
},
],
},
}}
/>
</Delayed>
</div>
</div>
);
};
export default CalorieChart;
Наконец, давайте поработаем над компонентом DisplayFoodList, поэтому сначала импортируйте ссылку из response-router, затем импортируйте пакет axios, затем импортируйте два ранее созданных компонента диаграммы, затем создайте компонент FoodTrack внутри файла DisplayFoodList и добавьте следующий код, и наконец, создайте три функции с именами DisplayFoodList, deleteMeal и malList и используйте все импортированные данные внутри оператора return и не забудьте вызвать функцию mailList внутри tbody. Наконец, если вы правильно выполнили все шаги, то ваш компонент DisplayFoodList должно напоминать следующее.
//components/DisplayFoodList
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import CalorieChart from "../CalorieChart";
import UserChart from "../UserChart";
const FoodTrack = (props) => (
<tr>
<td>
<Link to={"/edit/" + props.meal._id} style={{ color: " #a04949" }}>
<img
src="https://user-images.githubusercontent.com/37651620/142769270-6128d45e-3650-4b66-bc0b-a76e3991fa1f.png"
alt="edit"
style={{ height: "40px" }}
/>
</Link>{" "}
|{" "}
<a
href="#"
onClick={() => {
props.deleteMeal(props.meal._id);
window.location.reload(false);
}}
style={{ color: " #a04949" }}
>
<img
src="https://user-images.githubusercontent.com/37651620/142769328-23d55107-8bed-4fa0-92b8-cca7df931083.png"
alt="edit"
style={{ height: "40px" }}
/>
</a>
</td>
<td>{props.meal.username}</td>
<td>{props.meal.description}</td>
<td>{props.meal.calories}</td>
<td>{props.meal.date.substring(0, 10)}</td>
</tr>
);
const DisplayFoodList = () => {
const [foods, setFoods] = useState([]);
useEffect(() => {
axios
.get("http://localhost:5000/calorie/")
.then((response) => {
setFoods(response.data);
})
.catch((error) => {
console.log(error);
});
}, []);
function deleteMeal(id) {
axios.delete("http://localhost:5000/calorie/" + id).then((response) => {
console.log(response.data);
});
setFoods(foods.filter((el) => el._id !== id));
}
const mealList = () => {
return foods.map((currentmeal) => {
return (
<FoodTrack
meal={currentmeal}
deleteMeal={deleteMeal}
key={currentmeal._id}
/>
);
});
};
return (
<>
<>
<div className="container">
<div className="card border-0 shadow my-4">
<div className="card-body p-5">
<h3 style={{ textAlign: "center", marginBottom: "15px" }}>
Calorie Journal
</h3>
<table className="table" style={{ textAlign: "center" }}>
<thead className="thead" style={{ backgroundColor: "#8661d1" }}>
<tr>
<th>Edit/Delete</th>
<th>👤 Username</th>
<th>📙 Description</th>
<th>🔥 Calories</th>
<th>📅 Date</th>
</tr>
</thead>
<tbody>{mealList()}</tbody>
</table>
</div>
</div>
</div>
<div className="container">
<div
className="card border-0 shadow my-2"
style={{ padding: "2rem" }}
>
<div className="card-body p-1"></div>
<UserChart />
<CalorieChart />
</div>
</div>
</>
</>
);
};
export default DisplayFoodList;
Мы рассмотрели множество вопросов, чтобы предоставить вам информацию, необходимую для создания полноценного стекового приложения MERN с нуля.
Здесь вы можете найти весь исходный код.
Contribute to pramit-marattha/MERN-saas-project development by creating an account on GitHub.