Diese Website verwendet Cookies, damit wir dir die bestmögliche Benutzererfahrung bieten können. Cookie-Informationen werden in deinem Browser gespeichert und führen Funktionen aus, wie das Wiedererkennen von dir, wenn du auf unsere Website zurückkehrst, und hilft unserem Team zu verstehen, welche Abschnitte der Website für dich am interessantesten und nützlichsten sind.
Nachdem nun Wunderlist ja leider Geschichte ist wollen wir uns heute mal anschauen, 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:
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:
und schließlich das ganze noch via npm install
installieren.♀️
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
)
REACT_APP_SUPABASE_URL=<meine-url>
REACT_APP_SUPABASE_KEY=<mein-key>
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.
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.
Kommentare