О чем эта статья?
В этой статье вы узнаете, как создать доску Канбан так же, как в JIRA, Monday и Trello. Мы сделаем это с помощью красивой функции перетаскивания, используя React, Socket.io и React Beautiful DND. Пользователи смогут входить в систему, создавать и обновлять различные задачи, а также добавлять комментарии.
Novu — первая инфраструктура уведомлений с открытым исходным кодом
Просто краткая справка о нас. Novu — первая инфраструктура уведомлений с открытым исходным кодом. Мы в основном помогаем управлять всеми уведомлениями о продуктах. Это может быть In-App (значок колокольчика, как у вас в сообществе разработчиков — веб-сокеты), электронные письма, SMS и так далее.
Я был бы очень рад, если бы вы могли дать нам звезду! Это поможет мне делать больше статей каждую неделю 🚀
https://github.com/novuhq/novu
Мы также отправим классные подарки во время Хактоберфеста 😇
Что такое Socket.io?
Socket.io — это популярная библиотека JavaScript, которая позволяет нам создавать двустороннюю связь в режиме реального времени между веб-браузерами и сервером Node.js. Это высокопроизводительная и надежная библиотека, оптимизированная для обработки больших объемов данных с минимальной задержкой. Он следует протоколу WebSocket и обеспечивает улучшенные функциональные возможности, такие как возврат к длительному опросу HTTP или автоматическое повторное подключение, что позволяет нам создавать эффективные приложения в реальном времени.
Как создать соединение в реальном времени с помощью Socket.io и React.js
Здесь мы настроим среду проекта для проекта. Вы также узнаете, как добавить Socket.io в приложение React и Node.js и соединить оба сервера разработки для связи в реальном времени через Socket.io.
Создайте папку проекта, содержащую две подпапки с именами client и server.
mkdir todo-list
cd todo-list
mkdir client server
Перейдите в папку клиента через свой терминал и создайте новый проект React.js.
cd client
npx create-react-app ./
Установите клиентский API Socket.io и React Router. React Router — это библиотека JavaScript, которая позволяет нам перемещаться между страницами в приложении React.
npm install socket.io-client react-router-dom
Удалите лишние файлы, такие как логотип и тестовые файлы, из приложения React и обновите файл App.js
, чтобы отобразить Hello World, как показано ниже.
function App() {
return (
<div>
<p>Hello World!</p>
</div>
);
}
export default App;
Перейдите в папку сервера и создайте файл package.json
.
cd server & npm init -y
Установите Express.js, CORS, Nodemon и Socket.io Server API.
Express.js — это быстрый минималистичный фреймворк, предоставляющий несколько функций для создания веб-приложений на Node.js. CORS — это пакет Node.js, который обеспечивает связь между разными доменами.
Nodemon — это инструмент Node.js, который автоматически перезапускает сервер после обнаружения изменений в файле, а Socket.io позволяет нам настроить соединение в реальном времени на сервере.
npm install express cors nodemon socket.io
Создайте файл index.js
— точку входа на веб-сервер.
touch index.js
Настройте простой сервер Node.js с помощью Express.js. Приведенный ниже фрагмент кода возвращает объект JSON при посещении http://localhost:4000/api
в браузере.
//👇🏻index.js const express = require("express"); const app = express(); const PORT = 4000;
app.use(express.urlencoded({ extended: true })); app.use(express.json());
app.get("/api", (req, res) => { res.json({ message: "Hello world", }); });
app.listen(PORT, () => { console.log(`Server listening on ${PORT}`); });
Импортируйте библиотеки HTTP и CORS, чтобы разрешить передачу данных между клиентским и серверным доменами.
const express = require("express"); const app = express(); const PORT = 4000;
app.use(express.urlencoded({ extended: true })); app.use(express.json());
//New imports const http = require("http").Server(app); const cors = require("cors");
app.use(cors());
app.get("/api", (req, res) => { res.json({ message: "Hello world", }); });
http.listen(PORT, () => { console.log(`Server listening on ${PORT}`); });
Затем добавьте Socket.io в проект, чтобы создать соединение в реальном времени. Перед app.get()
блоком скопируйте приведенный ниже код.
//New imports ..... const socketIO = require('socket.io')(http, { cors: { origin: "http://localhost:3000" } });
//Add this before the app.get() block socketIO.on('connection', (socket) => { console.log(`⚡: ${socket.id} user just connected!`); socket.on('disconnect', () => { socket.disconnect() console.log('🔥: A user disconnected'); }); });
Из приведенного выше фрагмента кода функция socket.io("connection")
устанавливает соединение с приложением React, затем создает уникальный идентификатор для каждого сокета и записывает идентификатор в консоль всякий раз, когда пользователь посещает веб-страницу.
Когда вы обновляете или закрываете веб-страницу, сокет запускает событие разъединения, показывающее, что пользователь отключился от сокета.
Настройте Nodemon, добавив команду запуска в список скриптов в файле package.json
. Фрагмент кода ниже запускает сервер с помощью Nodemon.
//In server/package.json
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon index.js" },
Теперь вы можете запустить сервер с помощью Nodemon, используя приведенную ниже команду.
npm start
Создание пользовательского интерфейса
Здесь мы создадим пользовательский интерфейс для приложения. Оно разделено на три страницы: страница входа, страница задач — центральная часть приложения и страница комментариев — где пользователи могут комментировать каждую задачу.
Перейдите в client/src
и создайте папку компонентов, содержащую файлы Login.js
, Task.js
и Comments.js
.
cd client/src
mkdir components
cd components
touch Login.js Task.js Comments.js
Обновите файл App.js
, чтобы отображать вновь созданные компоненты на разных маршрутах через React Router.
import { BrowserRouter, Route, Routes } from "react-router-dom"; import Comments from "./components/Comments"; import Task from "./components/Task"; import Login from "./components/Login";
function App() { return ( <BrowserRouter> <Routes> <Route path='/' element={<Login />} /> <Route path='/task' element={<Task />} /> <Route path='/comments/:category/:id' element={<Comments />} /> </Routes> </BrowserRouter> ); }
export default App;
Перейдите в файл src/index.css
и скопируйте приведенный ниже код. Он содержит весь CSS, необходимый для стилизации этого проекта.
@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap"); * { font-family: "Space Grotesk", sans-serif; box-sizing: border-box; } a { text-decoration: none; } body { margin: 0; padding: 0; } .navbar { width: 100%; background-color: #f1f7ee; height: 10vh; border-bottom: 1px solid #ddd; display: flex; align-items: center; justify-content: space-between; padding: 20px; } .form__input { min-height: 20vh; display: flex; align-items: center; justify-content: center; } .input { margin: 0 5px; width: 50%; padding: 10px 15px; } .addTodoBtn { width: 150px; padding: 10px; cursor: pointer; background-color: #367e18; color: #fff; border: none; outline: none; height: 43px; } .container { width: 100%; min-height: 100%; display: flex; align-items: center; justify-content: space-between; padding: 10px; }
.completed__wrapper, .ongoing__wrapper, .pending__wrapper { width: 32%; min-height: 60vh; display: flex; flex-direction: column; padding: 5px; } .ongoing__wrapper > h3, .pending__wrapper > h3, .completed__wrapper > h3 { text-align: center; text-transform: capitalize; } .pending__items { background-color: #eee3cb; } .ongoing__items { background-color: #d2daff; } .completed__items { background-color: #7fb77e; } .pending__container, .ongoing__container, .completed__container { width: 100%; min-height: 55vh; display: flex; flex-direction: column; padding: 5px; border: 1px solid #ddd; border-radius: 5px; } .pending__items, .ongoing__items, .completed__items { width: 100%; border-radius: 5px; margin-bottom: 10px; padding: 15px; } .comment { text-align: right; font-size: 14px; cursor: pointer; color: rgb(85, 85, 199); } .comment:hover { text-decoration: underline; } .comments__container { padding: 20px; } .comment__form { width: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; margin-bottom: 30px; } .comment__form > label { margin-bottom: 15px; } .comment__form textarea { width: 80%; padding: 15px; margin-bottom: 15px; } .commentBtn { padding: 10px; width: 200px; background-color: #367e18; outline: none; border: none; color: #fff; height: 45px; cursor: pointer; } .comments__section { width: 100%; display: flex; align-items: center; justify-content: center; flex-direction: column; }
.login__form { width: 100%; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; } .login__form > label { margin-bottom: 15px; } .login__form > input { width: 70%; padding: 10px 15px; margin-bottom: 15px; } .login__form > button { background-color: #367e18; color: #fff; padding: 15px; cursor: pointer; border: none; font-size: 16px; outline: none; width: 200px; }
Страница входа
Здесь приложение принимает имя пользователя и сохраняет его в локальном хранилище для идентификации.
Обновите файл Login.js
, как показано ниже:
import React, { useState } from "react"; import { useNavigate } from "react-router-dom";
const Login = () => { const [username, setUsername] = useState(""); const navigate = useNavigate();
const handleLogin = (e) => { e.preventDefault(); //👇🏻 saves the username to localstorage localStorage.setItem("userId", username); setUsername(""); //👇🏻 redirects to the Tasks page. navigate("/tasks"); }; return ( <div className='login__container'> <form className='login__form' onSubmit={handleLogin}> <label htmlFor='username'>Provide a username</label> <input type='text' name='username' id='username' required onChange={(e) => setUsername(e.target.value)} value={username} /> <button>SIGN IN</button> </form> </div> ); };
export default Login;
Страница задач
Здесь я проведу вас через создание веб-макета для страницы задач. На изображении ниже представлен макет страницы.
Разделите макет на три компонента, а именно: Nav.js
, AddTask.js
— раздел ввода формы и TasksContainer.js
— содержащий задачи.
cd src/components
touch Nav.js AddTask.js TasksContainer.js
Визуализируйте компоненты в файле Task.js
.
import React from "react"; import AddTask from "./AddTask"; import TasksContainer from "./TasksContainer"; import Nav from "./Nav"; import socketIO from "socket.io-client";
/* 👇🏻 Pass Socket.io into the required components where communications are made with the server */ const socket = socketIO.connect("http://localhost:4000");
const Task = () => { return ( <div> <Nav /> <AddTask socket={socket} /> <TasksContainer socket={socket} /> </div> ); };
export default Task;
Скопируйте приведенный ниже код в файл Nav.js
.
import React from "react";
const Nav = () => { return ( <nav className='navbar'> <h3>Team's todo list</h3> </nav> ); }; export default Nav;
Обновите файл AddTask.js
, как показано ниже:
import React, { useState } from "react";
const AddTask = ({ socket }) => { const [task, setTask] = useState("");
const handleAddTodo = (e) => { e.preventDefault(); //👇🏻 Logs the task to the console console.log({ task }); setTask(""); }; return ( <form className='form__input' onSubmit={handleAddTodo}> <label htmlFor='task'>Add Todo</label> <input type='text' name='task' id='task' value={task} className='input' required onChange={(e) => setTask(e.target.value)} /> <button className='addTodoBtn'>ADD TODO</button> </form> ); };
export default AddTask;
Скопируйте приведенный ниже код в файл TasksContainer.js
. Он отображает три родительских элемента для ожидающих, текущих и завершенных задач.
import React from "react"; import { Link } from "react-router-dom";
const TasksContainer = ({ socket }) => { return ( <div className='container'> <div className='pending__wrapper'> <h3>Pending Tasks</h3> <div className='pending__container'> <div className='pending__items'> <p>Debug the Notification center</p> <p className='comment'> <Link to='/comments'>2 Comments</Link> </p> </div> </div> </div>
<div className='ongoing__wrapper'> <h3>Ongoing Tasks</h3> <div className='ongoing__container'> <div className='ongoing__items'> <p>Create designs for Novu</p> <p className='comment'> <Link to='/comments'>Add Comment</Link> </p> </div> </div> </div>
<div className='completed__wrapper'> <h3>Completed Tasks</h3> <div className='completed__container'> <div className='completed__items'> <p>Debug the Notification center</p> <p className='comment'> <Link to='/comments'>2 Comments</Link> </p> </div> </div> </div> </div> ); };
export default TasksContainer;
Поздравляем!💃🏻 Макет настроен. Итак, давайте создадим простой шаблон для страницы комментариев.
Страница комментариев
Скопируйте приведенный ниже код в файл Comments.js
. Он записывает комментарий и имя пользователя в консоль.
import React, { useEffect, useState } from "react"; import socketIO from "socket.io-client"; import { useParams } from "react-router-dom";
const socket = socketIO.connect("http://localhost:4000");
const Comments = () => { const [comment, setComment] = useState("");
const addComment = (e) => { e.preventDefault(); console.log({ comment, userId: localStorage.getItem("userId"), }); setComment(""); };
return ( <div className='comments__container'> <form className='comment__form' onSubmit={addComment}> <label htmlFor='comment'>Add a comment</label> <textarea placeholder='Type your comment...' value={comment} onChange={(e) => setComment(e.target.value)} rows={5} id='comment' name='comment' required ></textarea> <button className='commentBtn'>ADD COMMENT</button> </form>
<div className='comments__section'> <h2>Existing Comments</h2> <div></div> </div> </div> ); };
export default Comments;
Теперь пользовательский интерфейс завершен. Затем давайте добавим React Beautiful DND в приложение, чтобы включить функцию перетаскивания.
Как добавить функцию перетаскивания с помощью React Beautiful DND
Здесь вы узнаете, как добавить функцию перетаскивания с помощью React Beautiful DND и установить связь между приложением React и сервером Socket.io Node.js.
Как работает React Beautiful DND?
React Beautiful DND — это высокопроизводительная библиотека, которая позволяет нам выбирать и перетаскивать элемент из его текущей позиции в другую позицию на странице.
На изображении выше показано, как настроить React Beautiful DND. Вы должны обернуть все перетаскиваемые и сбрасываемые элементы в файл <DragDropContext/>
. Компонент <Droppable/>
содержит перетаскиваемые элементы, размещенные внутри компонента <Draggable/>
.
Делаем задачи перетаскиваемыми и сбрасываемыми с помощью React Beautiful DND
Здесь вы узнаете, как добавить React Beautiful DND в приложение React и сделать задачи перемещаемыми из одной категории в другую (ожидающие, текущие и завершенные).
Установите React Beautiful DND и убедитесь, что вы не используете React в строгом режиме. (проверьте src/index.js
).
npm install react-beautiful-dnd
Откройте файл server/index.js
и создайте объект, содержащий все фиктивные данные для каждой категории задач.
//👇🏻 server/index.js
//👇🏻 Generates a random string const fetchID = () => Math.random().toString(36).substring(2, 10);
//👇🏻 Nested object let tasks = { pending: { title: "pending", items: [ { id: fetchID(), title: "Send the Figma file to Dima", comments: [], }, ], }, ongoing: { title: "ongoing", items: [ { id: fetchID(), title: "Review GitHub issues", comments: [ { name: "David", text: "Ensure you review before merging", id: fetchID(), }, ], }, ], }, completed: { title: "completed", items: [ { id: fetchID(), title: "Create technical contents", comments: [ { name: "Dima", text: "Make sure you check the requirements", id: fetchID(), }, ], }, ], }, };
//👇🏻 host the tasks object via the /api route app.get("/api", (req, res) => { res.json(tasks); });
Затем выберите задачи из файла TasksContainer.js
. Фрагмент кода ниже преобразует объект tasks в массив перед визуализацией компонента.
import React, { useState, useEffect } from "react"; import { Link } from "react-router-dom";
const TasksContainer = () => { const [tasks, setTasks] = useState({});
useEffect(() => { function fetchTasks() { fetch("http://localhost:4000/api") .then((res) => res.json()) .then((data) => { console.log(data); setTasks(data); }); } fetchTasks(); }, []);
return ( <div className='container'> {/* 👇🏻 Returns an array of each tasks (Uncomment to view the data structure)
{Object.entries(tasks).map((task) => console.log(task))} */}
{Object.entries(tasks).map((task) => ( <div className={`${task[1].title.toLowerCase()}__wrapper`} key={task[1].title} > <h3>{task[1].title} Tasks</h3> <div className={`${task[1].title.toLowerCase()}__container`}> {task[1].items.map((item, index) => ( <div className={`${task[1].title.toLowerCase()}__items`} key={item.id} > <p>{item.title}</p> <p className='comment'> <Link to='/comments'> {item.comments.length > 0 ? `View Comments` : "Add Comment"} </Link> </p> </div> ))} </div> </div> ))} </div> ); };
export default TasksContainer;
Импортируйте необходимые компоненты из «react-beautiful-dnd» в файл TasksContainer.js
.
//👇🏻 At the top of the TasksContainer.js file
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
Обновите файл TaskContainer.js
, как показано ниже:
return (
<div className='container'>
{/** --- 👇🏻 DragDropContext ---- */}
<DragDropContext onDragEnd={handleDragEnd}>
{Object.entries(tasks).map((task) => (
<div
className={`${task[1].title.toLowerCase()}__wrapper`}
key={task[1].title}
>
<h3>{task[1].title} Tasks</h3>
<div className={`${task[1].title.toLowerCase()}__container`}>
{/** --- 👇🏻 Droppable --- */}
<Droppable droppableId={task[1].title}>
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{task[1].items.map((item, index) => (
{/** --- 👇🏻 Draggable --- */}
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`${task[1].title.toLowerCase()}__items`}
>
<p>{item.title}</p>
<p className='comment'>
<Link to={`/comments/${task[1].title}/${item.id}`}>
{item.comments.length > 0
? `View Comments`
: "Add Comment"}
</Link>
</p>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
</div>
))}
</DragDropContext>
</div>
);
- Из фрагмента кода выше:
DragDropContext
обертывает весь контейнер перетаскивания, а Droppable представляет родительский элемент для перетаскиваемых элементов.- Компоненты Draggable и Droppable принимают перетаскиваемый идентификатор. Они также принимают дочерний элемент,
provided
, который позволяет нам ссылаться и отображать каждый элемент как перетаскиваемый элемент. - Не стесняйтесь разделить код на разные компоненты и нажмите здесь, чтобы узнать больше о React Beautiful DND.
DragDropContext
принимает реквизит onDragEnd
, который срабатывает сразу после перетаскивания элемента.
//👇🏻 This function is the value of the onDragEnd prop const handleDragEnd = ({ destination, source }) => { if (!destination) return; if ( destination.index === source.index && destination.droppableId === source.droppableId ) return;
socket.emit("taskDragged", { source, destination, }); };
Приведенный выше фрагмент кода принимает место назначения и источник перетаскиваемого элемента, проверяет, был ли он перетащен в место назначения, которое можно перетаскивать, и не совпадают ли источник и место назначения перед отправкой сообщения на сервер Node.js через Socket.io.
Создайте прослушиватель события taskDragged
на серверной части.
socketIO.on("connection", (socket) => { console.log(`⚡: ${socket.id} user just connected!`);
socket.on("taskDragged", (data) => { console.log(data); });
socket.on("disconnect", () => { socket.disconnect(); console.log("🔥: A user disconnected"); }); });
Давайте кратко рассмотрим данные, возвращаемые после перетаскивания элемента:
Фрагмент кода ниже показывает, что элемент перемещен из категории «Ожидание» в категорию «Текущие». Индекс также изменился с 0 на 1.
{
source: { index: 0, droppableId: 'pending' },
destination: { droppableId: 'ongoing', index: 1 }
}
Затем сделайте так, чтобы перетаскиваемый элемент оставался в месте назначения. Обновите прослушиватель taskDragged
, как показано ниже:
socket.on("taskDragged", (data) => { const { source, destination } = data;
//👇🏻 Gets the item that was dragged const itemMoved = { ...tasks[source.droppableId].items[source.index], }; console.log("DraggedItem>>> ", itemMoved);
//👇🏻 Removes the item from the its source tasks[source.droppableId].items.splice(source.index, 1);
//👇🏻 Add the item to its destination using its destination index tasks[destination.droppableId].items.splice(destination.index, 0, itemMoved);
//👇🏻 Sends the updated tasks object to the React app socket.emit("tasks", tasks);
/* 👇🏻 Print the items at the Source and Destination console.log("Source >>>", tasks[source.droppableId].items); console.log("Destination >>>", tasks[destination.droppableId].items); */ });
Создайте прослушиватель для события tasks
в компоненте TasksContainer
.
useEffect(() => {
socket.on("tasks", (data) => setTasks(data));
}, [socket]);
Поздравляем!🎉 Теперь вы можете перетаскивать элементы из одной категории в другую.
Как создавать новые задачи
В этом разделе я проведу вас через создание новых задач из приложения React.
Обновите файл AddTask.js
, чтобы отправить новую задачу на внутренний сервер.
import React, { useState } from "react";
const AddTask = ({ socket }) => { const [task, setTask] = useState("");
const handleAddTodo = (e) => { e.preventDefault(); //👇🏻 sends the task to the Socket.io server socket.emit("createTask", { task }); setTask(""); }; return ( <form className='form__input' onSubmit={handleAddTodo}> <label htmlFor='task'>Add Todo</label> <input type='text' name='task' id='task' value={task} className='input' required onChange={(e) => setTask(e.target.value)} /> <button className='addTodoBtn'>ADD TODO</button> </form> ); };
export default AddTask;
Создайте прослушиватель события createTask
на внутреннем сервере и добавьте элемент в объект tasks
.
socketIO.on("connection", (socket) => { console.log(`⚡: ${socket.id} user just connected!`);
socket.on("createTask", (data) => { // 👇🏻 Constructs an object according to the data structure const newTask = { id: fetchID(), title: data.task, comments: [] }; // 👇🏻 Adds the task to the pending category tasks["pending"].items.push(newTask); /* 👇🏻 Fires the tasks event for update */ socket.emit("tasks", tasks); }); //...other listeners });
Заполнение страницы комментариев
В этом разделе вы узнаете, как добавлять и получать комментарии к каждой задаче.
Обновите файл Comments.js
, как показано ниже:
import React, { useEffect, useState } from "react"; import socketIO from "socket.io-client"; import { useParams } from "react-router-dom";
const socket = socketIO.connect("http://localhost:4000");
const Comments = () => { const { category, id } = useParams(); const [comment, setComment] = useState("");
const addComment = (e) => { e.preventDefault(); /* 👇🏻 sends the comment, the task category, item's id and the userID. */ socket.emit("addComment", { comment, category, id, userId: localStorage.getItem("userId"), }); setComment(""); };
return ( <div className='comments__container'> <form className='comment__form' onSubmit={addComment}> <label htmlFor='comment'>Add a comment</label> <textarea placeholder='Type your comment...' value={comment} onChange={(e) => setComment(e.target.value)} rows={5} id='comment' name='comment' required ></textarea> <button className='commentBtn'>ADD COMMENT</button> </form> <div className='comments__section'> <h2>Existing Comments</h2> <div></div> </div> </div> ); };
export default Comments;
Напомним, что маршрут страницы комментариев — /comments/:category/:id
; приведенный выше фрагмент кода извлекает категорию элемента и его идентификатор из URL-адреса страницы, а затем отправляет категорию элемента, идентификатор, идентификатор пользователя и комментарий на сервер Node.js.
Затем создайте прослушиватель событий на сервере Node.js, который добавит комментарий к конкретной задаче через ее идентификатор.
socket.on("addComment", (data) => {
const { category, userId, comment, id } = data;
//👇🏻 Gets the items in the task's category
const taskItems = tasks[category].items;
//👇🏻 Loops through the list of items to find a matching ID
for (let i = 0; i < taskItems.length; i++) {
if (taskItems[i].id === id) {
//👇🏻 Then adds the comment to the list of comments under the item (task)
taskItems[i].comments.push({
name: userId,
text: comment,
id: fetchID(),
});
//👇🏻 sends a new event to the React app
socket.emit("comments", taskItems[i].comments);
}
}
});
Получить комментарии через Socket.io.
const Comments = () => { const { category, id } = useParams(); const [comment, setComment] = useState(""); const [commentList, setCommentList] = useState([]);
//👇🏻 Listens to the comments event useEffect(() => { socket.on("comments", (data) => setCommentList(data)); }, []);
//...other listeners return ( <div className='comments__container'> <form className='comment__form' onSubmit={addComment}> ... </form>
{/** 👇🏻 Displays all the available comments*/} <div className='comments__section'> <h2>Existing Comments</h2> {commentList.map((comment) => ( <div key={comment.id}> <p> <span style={{ fontWeight: "bold" }}>{comment.text} </span>by{" "} {comment.name} </p> </div> ))} </div> </div> ); };
export default Comments;
Наконец, добавьте этот хук useEffect
, чтобы получать комментарии, когда страница загружается в браузер.
useEffect(() => {
socket.emit("fetchComments", { category, id });
}, [category, id]);
Прослушайте событие на бэкенде и инициируйте событие комментариев, чтобы вернуть список комментариев, соответствующих идентификатору и категории элемента.
socket.on("fetchComments", (data) => {
const { category, id } = data;
const taskItems = tasks[category].items;
for (let i = 0; i < taskItems.length; i++) {
if (taskItems[i].id === id) {
socket.emit("comments", taskItems[i].comments);
}
}
});
Поздравляем!💃🏻 Мы завершили этот проект.
ДОПОЛНИТЕЛЬНО: Отправка уведомлений с Novu
Если вы хотите добавить уведомления в приложение, когда пользователь добавляет комментарий или новую задачу, вы можете легко сделать это с помощью Novu в компоненте Nav.js
.
Novu позволяет добавлять различные типы уведомлений, такие как электронная почта, SMS и уведомления в приложении.
Как добавить Novu в приложение React и Node.js
Чтобы добавить уведомление в приложении, установите SDK Novu Node.js на сервер и Центр уведомлений в приложении React.
👇🏻 Install on the client npm install @novu/notification-center
👇🏻 Install on the server npm install @novu/node
Создайте проект Novu, запустив приведенный ниже код. Вам доступна персонализированная панель управления.
👇🏻 Install on the client
npx novu init
Вам нужно будет войти в Github перед созданием проекта Novu. Фрагмент кода ниже содержит шаги, которые вы должны выполнить после запуска npx novu init
.
Now let's setup your account and send your first notification ❓ What is your application name? Devto Clone ❓ Now lets setup your environment. How would you like to proceed? > Create a free cloud account (Recommended) ❓ Create your account with: > Sign-in with GitHub ❓ I accept the Terms and Condidtions (https://novu.co/terms) and have read the Privacy Policy (https://novu.co/privacy) > Yes ✔️ Create your account successfully.
We've created a demo web page for you to see novu notifications in action. Visit: http://localhost:57807/demo to continue
Посетите демонстрационную веб-страницу http://localhost:57807/demo
, скопируйте свой идентификатор подписчика со страницы и нажмите кнопку «Пропустить руководство». Мы будем использовать его позже в этом уроке.
Обновите файл components/Nav.js
, чтобы он содержал Novu и его необходимые элементы для уведомлений в приложении из документации.
import React from "react"; import { NovuProvider, PopoverNotificationCenter, NotificationBell, } from "@novu/notification-center"; import { useNavigate } from "react-router-dom";
const Nav = () => { const navigate = useNavigate();
const onNotificationClick = (notification) => navigate(notification.cta.data.url); return ( <nav className='navbar'> <h3>Team's todo list</h3> <div> <NovuProvider subscriberId='<SUBSCRIBER_ID>' applicationIdentifier='<APP_ID>' > <PopoverNotificationCenter onNotificationClick={onNotificationClick} colorScheme='light' > {({ unseenCount }) => ( <NotificationBell unseenCount={unseenCount} /> )} </PopoverNotificationCenter> </NovuProvider> </div> </nav> ); };
export default Nav;
Приведенный выше фрагмент кода добавляет значок колокольчика уведомлений Novu в компонент Nav, что позволяет нам просматривать все уведомления из приложения.
💡 Для компонента NovuProvider
требуется ваш идентификатор подписчика, скопированный ранее из http://localhost:57807/demo
, и идентификатор вашего приложения, доступный в разделе Настройки в разделе Ключи API на Novu Manage Platform.
Далее давайте создадим рабочий процесс для приложения, который описывает функции, которые вы хотите добавить в приложение.
Выберите «Уведомление» на боковой панели «Разработка» и создайте шаблон уведомления. Выберите только что созданный шаблон, нажмите «Редактор рабочего процесса» и убедитесь, что рабочий процесс выглядит следующим образом:
На изображении выше Novu запускает механизм дайджеста перед отправкой уведомления в приложении.
Novu Digest позволяет нам контролировать, как мы хотим отправлять уведомления в приложении. Он собирает несколько триггерных событий и отправляет их как одно сообщение. Изображение выше отправляет уведомления каждые 2 минуты, и это может быть эффективно, когда у вас много пользователей и частые обновления.
Нажмите на шаг In-App
и отредактируйте шаблон уведомления, чтобы он содержал приведенное ниже содержимое.
{{userId}} added a new task.
💡 Novu позволяет добавлять в шаблоны динамический контент или данные с помощью движка шаблонов Handlebars. Данные для переменной имени пользователя будут вставлены в шаблон в качестве полезной нагрузки из запроса.
Сохраните шаблон, нажав кнопку Update
, и вернитесь в редактор кода.
Добавление Нову в приложение
Импортируйте Novu из пакета и создайте экземпляр, используя свой ключ API на сервере.
//server/index.js
const { Novu } = require("@novu/node"); const novu = new Novu("<YOUR_API_KEY>");
Создайте функцию, которая отправляет уведомление через Novu в приложение React.
const sendNotification = async (user) => { try { const result = await novu.trigger(<TEMPLATE_ID>, { to: { subscriberId: <SUBSCRIBER_ID>, }, payload: { userId: user, }, }); console.log(result); } catch (err) { console.error("Error >>>>", { err }); } };
//👇🏻 The function is called after a new task is created socket.on("createTask", (data) => { const newTask = { id: fetchID(), title: data.task, comments: [] }; tasks["pending"].items.push(newTask); socket.emit("tasks", tasks); //👇🏻 Triggers the notification via Novu sendNotification(data.userId); });
Приведенный выше фрагмент кода отправляет уведомление всем пользователям, когда в приложение добавляется новая задача.
Заключение
Итак, вы узнали, как настроить Socket.io в приложении React и Node.js, общаться между сервером и клиентом через Socket.io и перетаскивать элементы с помощью React Beautiful DND.
Это демонстрация того, что вы можете создать, используя Socket.io и React Beautiful DND. Не стесняйтесь улучшать приложение, добавляя аутентификацию, возможность назначать задачи конкретному пользователю и добавлять уведомления, когда пользователь оставляет комментарий.
Исходный код этого руководства доступен здесь: https://github.com/novuhq/blog/tree/main/react-beautiful-dnd-todo-list
Спасибо за чтение!
P.S. Novu присылает потрясающие подарки на Hacktoberfest! Рады, если вы можете поддержать нас, поставив нам звезду! ⭐️
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord.