DevGang
Авторизоваться

Как с помощью React создать совместные списки задач

Чтобы выразить сожаление по поводу закрытия Wunderlist, я подумал, что сегодня мы можем научиться создавать это - https://todo-zeta.now.sh/ - простой, совместный сервис списка задач в реальном времени. Пользователи могут создать новый список и поделиться им с друзьями / коллегами, чтобы заполнить их вместе.

Мы собираемся использовать функциональный React в качестве frontend и Supabase в качестве нашей базы данных и realtime движка.

Если вы хотите пропустить это, вы можете найти окончательный исходный код здесь:

Или давайте все-же разберем

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)&nbsp;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)&nbsp;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.

Источник:

#React #SQL
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить