Как с помощью React создать совместные списки задач
Чтобы выразить сожаление по поводу закрытия Wunderlist, я подумал, что сегодня мы можем научиться создавать это - https://todo-zeta.now.sh/ - простой, совместный сервис списка задач в реальном времени. Пользователи могут создать новый список и поделиться им с друзьями / коллегами, чтобы заполнить их вместе.
Мы собираемся использовать функциональный React в качестве frontend и Supabase в качестве нашей базы данных и realtime движка.
Если вы хотите пропустить это, вы можете найти окончательный исходный код здесь:
Website, docs, and client libraries. Follow to stay updated about our public Beta. - supabase/supabase
Или давайте все-же разберем
1) Создайте базу своего проекта
Для этого я использовал create-react-app:
npx create-react-app my-todo-app
Затем реструктурируйте ваш проект таким образом:
index.js
будет нашей точкой входа, где мы создадим новые списки, TodoList.js
будет списком, который мы создадим, и мы будем получать все наши данные в Store.js
.
Затем добавьте эти зависимости в package.json
:
и установить их все, запустив:
npm install
2) index.js
Добавьте в нашу функцию рендера базовый Router:
import { render } from 'react-dom'
render(
<div className="App">
<Router>
<Switch>
<Route exact path="/" component={Home} />
{/* Additional Routes go here */}
</Switch>
</Router>
</div>,
document.body
)
Далее вы настроить свой основной компонент:
const newList = async (history) => {
const list = await createList(uuidv4())
history.push(`/?uuid=${list.uuid}`)
}
const Home = (props) => {
const history = useHistory()
const uuid = queryString.parse(props.location.search).uuid
if (uuid) return TodoList(uuid)
else {
return (
<div className="container">
<div className="section">
<h1>Collaborative Task Lists</h1>
<small>
Powered by <a href="https://supabase.io">Supabase</a>
</small>
</div>
<div className="section">
<button
onClick={() => {
newList(history)
}}
>
new task list
</button>
</div>
</div>
)
}
}
Ключевая часть здесь createList(uuidv4())
заключается в том, что при нажатии кнопки создания списка мы генерируем случайным образом uuid
, а затем добавляем его к текущему URL в качестве параметра запроса, используя useHistory()
и history.push(...)
. Мы делаем это так, чтобы пользователь мог скопировать и поделиться URL-адресом просто передав его другому пользователю.
Кроме того, когда новый пользователь получает URL-адрес от своего друга - приложение знает, как искать конкретный список задач из БД, используя указанный uuid, вы можете увидеть это здесь:
const uuid = queryString.parse(props.location.search).uuid
if (uuid) return TodoList(uuid)
index.js <- я опустил часть скучного кода, поэтому возьмите остальное отсюда, чтобы закончить индексный файл.
3) Store.js
Теперь мы рассмотрим, как устанавливать, извлекать и прослушивать ваши данные в режиме реального времени, чтобы вы могли показывать новые и выполненные задачи совместно работающим пользователю без необходимости обновлять страницу.
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.REACT_APP_SUPABASE_URL,
process.env.REACT_APP_SUPABASE_KEY
)
Вам понадобится .env
файл в корне проекта, где мы будем хранить эти переменные:
REACT_APP_SUPABASE_URL=<my-url>
REACT_APP_SUPABASE_KEY=<my-key>
Чтобы получить учетные данные Supabase, перейдите в app.supabase.io , создайте новую организацию и проект и перейдите на страницу API, где вы найдете ключи:
Теперь перейдите на вкладку SQL, где мы создадим две наши таблицы Lists
и Tasks
используя встроенный интерпретатор SQL:
выполните эти два запроса, чтобы создать таблицы:
CREATE TABLE lists (
uuid text,
id bigserial PRIMARY KEY,
inserted_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL,
updated_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL
);
CREATE TABLE tasks (
task_text text NOT NULL,
complete boolean DEFAULT false,
id bigserial PRIMARY KEY,
list_id bigint REFERENCES lists NOT NULL,
inserted_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL,
updated_at timestamp without time zone DEFAULT timezone('utc' :: text, now()) NOT NULL
);
Теперь в Store.js
мы можем создать метод createList
, который мы вызвали в index.js
:
export const createList = async (uuid) => {
try {
let { body } = await supabase.from('lists').insert([{ uuid }])
return body[0]
} catch (error) {
console.log('error', error)
}
}
Вы можете взять остальную часть кода в Store.js
, но здесь есть и другие примечания:
как мы подписываемся на изменения в реальном времени в вашем списке задач:
supabase
.from(`tasks:list_id=eq.${list.id}`)
.on('INSERT', (payload) => handleNewTask(payload.new))
.on('UPDATE', (payload) => handleNewTask(payload.new))
.subscribe()
и как мы управляем состоянием, используя useState()
и useEffect
. Поначалу может быть немного сложно разобраться, поэтому не забудьте прочитать «Использование хук эффектов», чтобы понять, как все это сочетается.
4) TodoList.js
Для компонента TodoList мы начнем с импорта из стора:
import { useStore, addTask, updateTask } from './Store'
и затем вы можете использовать их, как и любую другую переменную состояния:
export const TodoList = (uuid) => {
const [newTaskText, setNewTaskText] = useState('')
const { tasks, setTasks, list } = useStore({ uuid })
return (
<div className="container">
<Link to="/">back</Link>
<h1 className="section">My Task List</h1>
<div className="section">
<label>Sharing url: </label>
<input type="text" readonly value={window.location.href} />
</div>
<div className={'field-row section'}>
<form
onSubmit={(e) => {
e.preventDefault()
setNewTaskText('')
}}
>
<input
id="newtask"
type="text"
value={newTaskText}
onChange={(e) => setNewTaskText(e.target.value)}
/>
<button type="submit" onClick={() => addTask(newTaskText, list.id)}>
add task
</button>
</form>
</div>
<div className="section">
{tasks
? tasks.map((task) => {
return (
<div key={task.id} className={'field-row'}>
<input
checked={task.complete ? true : ''}
onChange={(e) => {
tasks.find((t, i) => {
if (t.id === task.id) {
tasks[i].complete = !task.complete
return true
}
})
setTasks([...tasks])
updateTask(task.id, { complete: e.target.checked })
}}
type="checkbox"
id={`task-${task.id}`}
></input>
<label htmlFor={`task-${task.id}`}>
{task.complete ? <del>{task.task_text}</del> : task.task_text}
</label>
</div>
)
})
: ''}
</div>
</div>
)
}
если у вас есть все и работает, вы сможете запустить npm run start
и перейти к localhost:3000
, чтобы увидеть его в действии
Полный исходный код доступен на github
Supabase - это компания и сообщество с открытым исходным кодом, поэтому весь наш код доступен на github.com/supabase.