Springe zum Inhalt →

HowTo: Erstellen von kollaborativen Echtzeit-Aufgabenlisten in React

Nachdem nun Wunderlist ja leider Geschichte ist 😫, könnte es Sinn machen, dass wir heute mal lernen können, wie man sich diesen – https://community-tasks.now.sh/ – einfachen und kollaborativen Echtzeit-Tasklistendienst selbst erstellt.😮
Benutzer können damit eine neue Liste erstellen und diese mit Freunden / Kollegen teilen, um die Liste gemeinsam zu bearbeiten und vervollständigen.

Wir werden Functional React im Frontend und Supabase als Datenbank- und Echtzeit-Engine verwenden. (Was ist Supabase?)🤔

Wer die folgende Lektüre überspringen  möchte, findet den Quellcode der Anwendung hier: https://github.com/smartDevel/community-tasks

Anderenfalls steigen wir nun in die Details ein 👀🧠

1) Projektbasis erstellen

Dafür nutzen wir create-react-app

npx create-react-app community-tasks

Nachdem die initiale Struktur erstellt wurde, werden wir unser Projekt etwas (re-) strukturieren, so dass es im Code-Editor in etwa so aussieht:

VSCode Files
VSCode Filestruktur

index.js wird unser Einstiegspunkt sein, an dem wir neue Listen erstellen, TodoList.js wird die Liste sein, die wir erstellen, und wir werden alle unsere Daten aus Store.js abrufen.

Dann müssen wir noch folgende Abhängigkeiten in unsere package.json eintragen:

package.json abhängigkeiten
package.json abhängigkeiten

und schließlich das ganze noch via npm installinstallieren.🏋️‍♀️

2) index.js

Wir fügen unserem Basis-Router die Render-Funktion hinzu:

import { render } from 'react-dom'

render(
  <div className="App">
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        {/* weitere Routen ab hier */}
      </Switch>
    </Router>
  </div>,
  document.body
)

Danach bereiten wir unsere main-Komponente vor:

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;
  //GIF-Animation auf wordpress für Startscreen-Bild
  const imgStartscreen = "https://ways4eu.files.wordpress.com/2020/06/startscreengifanimationv04.gif";
  const lnkGithub = "https://github.com/smartDevel/community-tasks";
  const lnkTutorial =
    "https://dev.to/awalias/howto-build-collaborative-realtime-task-lists-in-react-4k52";
  const lnkDatabase = "https://supabase.io";

  if (uuid) return TodoList(uuid);
  else {
    return (
      <div className="container">
        <div className="section">
          <h1>Kollaborative Aufgabenlisten</h1>
          <small>
            Powered by <a href={lnkDatabase}>Supabase</a>
          </small>
        </div>
        <div className="section">
          <button
            className="bNew"
            onClick={() => {
              newList(history);
            }}
          >
            Neue Aufgabenliste
          </button>
        </div>
        <div className="section build">
          <h3>
            HowTo:
            <br />
            <a href={lnkTutorial}>Tutorial</a> | <a href={lnkGithub}>Github</a>
          </h3>
          {/* *** Alternative mit input-Feld *** */}
          <input
            type="image"
            src={imgStartscreen}
            onClick={() => {
              newList(history);
            }}
            alt="Neue Aufgabenliste"
          />
          {/* *** Alternative mit Button ***
          <button 
           onClick={() => {
            newList(history)
          }}>
            <img
              className="build-img"
              src={imgStartscreen}
              alt="learn how to build this"
            />
          </button>
          */}
          {/* *** Alternative mit Hyperlink ***
          <a onClick={() => {
              newList(history)
            }}>
            <img
              className="build-img"
              src={imgStartscreen}
              alt="learn how to build this"
            />

          </a> */}

        </div>
      </div>
    );
  }
};

Der wichtigste Teil hierbei ist, dass wir beim Klicken auf die Schaltfläche “Liste erstellen” eine Liste createList(uuidv4()) mit einer zufällig generierten UUID erstellen und diese dann mit useHistory () und history.push (...) als Abfrageparameter an die aktuelle URL anhängen. Wir tun dies, damit der Benutzer die URL aus der URL-Leiste kopieren und teilen kann.

Auch wenn ein neuer Benutzer einen Link mit uuid erhält – die Anwendung weiß, dass sie die spezifische Aufgabenliste aus der Datenbank mit der angegebenen UUID nachschlagen muss, dies ist hier zu sehen:

const uuid = queryString.parse(props.location.search).uuid;
if (uuid) return TodoList(uuid);

3) Store.js

Jetzt schauen wir uns an, wie wir die Daten in Echtzeit speichern, abrufen und überwachen, damit den adressierten Nutzern die jeweils aktuelle Liste mit allen neuen und bereits abgeschlossenen Aufgaben angezeigt wird, ohne dass diese die Seite erneut aktualisieren müssen.

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
)
Wir benötigen eine .env-Datei im Root- Verzeichnis, in der wir folgende Variablen speichern:
REACT_APP_SUPABASE_URL=<meine-url>
REACT_APP_SUPABASE_KEY=<mein-key>
Um die individuellen Supabase-Anmeldeinformationen abzurufen, rufen wir app.supabase.io auf, erstellen eine neue Organisation und ein neues Projekt und navigieren zur API-Seite, auf der die benötigten Informationen zu finden sind:
API-Ansicht supabase
API-Ansicht in supabase-Konfigurationsseite

In der Supabase Konfiguration gehen wir anschließend zur Registerkarte SQL, auf der wir unsere beiden Tabellen Listen (lists) und Aufgaben (tasks) mit dem integrierten SQL-Interpreter erstellen. Dort werden wir folgende SQL-Befehle zum Erstellen der Tabellen eintragen und ausführen lassen:

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
);

Nun können wir damit in Store.js die createList-Methode erstellen, die wir aus index.js aufrufen:

export const createList = async (uuid) => {
  try {
    let { body } = await supabase.from('lists').insert([{ uuid }])
    return body[0]
  } catch (error) {
    console.log('error', error)
  }
}

Hier ist der Rest des Codes zu Store.js abrufbar. Die anderen wichtigen Punkte in diesem Code sind

        supabase
          .from(`tasks:list_id=eq.${list.id}`)
          .on('INSERT', (payload) => handleNewTask(payload.new))
          .on('UPDATE', (payload) => handleNewTask(payload.new))
          .subscribe()

und wie wir den State mit useState () und useEffect verwalten.Das ist zunächst nicht ganz einfach zu verstehen. Sehr hilfreich ist es daher unbedingt die Erläuterung zum  Effekt-Hook durchzulesen, um zu verstehen, wie das alles zusammenpasst.

4) TodoList.js

Für die TodoList-Komponente importieren wir zunächst aus dem Store:

import { useStore, addTask, updateTask } from './Store'

und dann können wir diese  wie übliche State-Variablen verwenden:

export const TodoList = (uuid) => {
  const [newTaskText, setNewTaskText] = useState('')
  const { tasks, setTasks, list } = useStore({ uuid })

  return (
    <div className="container">
      <Link to="/">Zurück/Neustart</Link>
      <h1 className="section">✍🏼🧾✨Community Aufgabenliste✨🔜 ✔️</h1>
      <div className="section">
        <label>Aufruf-Link zum Bearbeiten und Anzeigen: </label>
        <input className="inputField" type="text" readOnly value={window.location.href} />
      </div>
      <div className={'field-row section'}>
        <form
          onSubmit={(e) => {
            e.preventDefault()
            setNewTaskText('')
          }}
        >
          <label>Neue Aufgabe hier eintragen und hinzufügen: </label>
          <input
            id="newtask"
            type="text"
            value={newTaskText}
            onChange={(e) => setNewTaskText(e.target.value)}
          />
          <button type="submit" onClick={() => (newTaskText ? addTask(newTaskText, list.id) : '')}>
            Aufgabe hinzufügen
          </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
                        } else {
                          return false
                        }
                      })
                      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 className="section">
        <small>
          Achtung: Bitte keine sensiblen Informationen hier eintragen, da die Liste öffentlich aufrufbar und einsehbar ist !
        </small>
      </div>
    </div>
  )
}

Wenn wir alles soweit zum Laufen gebracht haben, sollten wir nun in der Lage sein, npm run start auszuführen und im Browser die URL localhost: 3000 aufzurufen, um die Anwendung in Action zu sehen.

Der komplette Source-Code ist in gitHub frei verfügbar.
Haftungsausschluss/ Wichtiger Hinweis 🚩⚡:
Diese Demo wird ohne Benutzerauthentifizierung geliefert. Der Zugriff auf die Liste eines anderen Benutzers ist ohne Kenntnis der uuid zwar nicht ganz einfach aber trotzdem grundsätzlich möglich. Daher ist davon auszugehen, dass alles, was in die Aufgabenlisten eingetragen wird, öffentlich verfügbare Informationen sind.
[Gesamt: 0   Durchschnitt:  0/5]

Veröffentlicht in Coding Computers and IT

Kommentaren

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

%d Bloggern gefällt das: