API z bazą danych w kilka minut? – poznaj Supabase
Może jesteś developerem, który ma dość korzystania z serwisów typu MockAPI i poszukujesz narzędzia, które pozwoli stworzyć API z mającymi sens danymi dla wersji poglądowej Twojej aplikacji w kilka chwil?
A może jesteś początkującym programistą front-end, który chciałby, żeby jego projekty w portfolio wyróżniały się, a aplikacja TODO napisana w React’cie zaczęła przechowywać dane w innych miejscach, niż tylko w wewnętrznym stanie?
W obu tych przypadkach potrzebny jest Ci prosty (i najlepiej darmowy) sposób, aby nawet bez znajomości technologii back-endowych, stworzyć zaplecze dla Twojego niesamowitego projektu!
API – czyli interfejs programowania aplikacji, to nic innego jak zestaw reguł opisujący, w jaki sposób poszczególne aplikacje (lub części naszego systemu) komunikują się ze sobą.
Czym jest Supabase?
Tworzenie projektu w Supabase
Jeżeli nie masz tam konta, to zachęcam Cię do utworzenia go. Github, to podstawowe narzędzie, które pozwoli przyszłemu pracodawcy zobaczyć projekty, które tworzyłeś. Poza tym, znajomość git’a, to must have w większości firm, dlatego tym bardziej warto rozpocząć swoją przygodę z nim jak najwcześniej.
W Supabase masz możliwość tworzenia organizacji, a następnie zakładania w nich projektów. Utwórz nową organizację o dowolnej nazwie – w moim przypadku będzie to Dogtronic 🙂 – i przejdź do wybrania jej ustawień.
Wybierz z listy serwer znajdujący się najbliżej Twojej lokacji i podaj nazwę projektu oraz hasło dla swojej bazy danych. Hasło to powinno być silne i przechowywane w bezpiecznym miejscu, aby uniemożliwić innym dostęp do Twojej bazy.
Po zakończeniu konfiguracji, zobaczysz panel główny swojego projektu. Znajdziesz tu klucz publiczny dla swojego API, klucz prywatny, a także listę przykładowych projektów w wielu frameworkach i językach programowania.
Klucz prywatny (oznaczony service_role, secret) pozwala na dokonywanie dowolnych zmian w bazie danych, włącznie z usunięciem całej jej zawartości. Nie udostępniaj go nikomu!
Tworzenie bazy danych
W wyświetlonym panelu, pierwszy z pokazanych kafelków powinien być zatytułowany Database. Z tego miejsca możesz utworzyć nową bazę z użyciem edytora graficznego (zalecany dla początkujących) lub z edytora SQL, gdzie będziesz mógł wykonywać zapytania oraz tworzyć tabelę z użyciem języka SQL.
Masz tutaj także link do dokumentacji, do której warto wracać w momentach zacięcia się podczas pracy. Dla ułatwienia w naszym przypadku wykorzystamy edytor graficzny Table Editor.
Przy okazji, zastanówmy się, jak powinna wyglądać baza danych dla naszego projektu. Pierwszą i najważniejszą z tabel powinniśmy przeznaczyć na nasze zadania na liście. Chcielibyśmy zapewne również, aby użytkownicy mogli przy rejestracji podać swoje podstawowe dane (imię, nazwisko, może pseudonim?).
Dodatkową funkcjonalnością może być także kategoria zadania, którą użytkownik będzie mógł zdefiniować samodzielnie, aby następnie móc pogrupować swoje zadania. Zobaczmy zatem, jak taka baza mogłaby wyglądać na schemacie ERD.
W Supabase, tabela użytkowników, którzy zarejestrują się w naszej aplikacji (nazwana users) jest generowana automatycznie. Aby dodać jednak dodatkowe pola, które mogą być potrzebne w naszej aplikacji, najlepiej jest utworzyć dodatkową tabelę, a następnie powiązać ją relacją z tabelą domyślną.

Przy tworzeniu tabel zostanie pokazana opcja „Enable Row Level Security (RLS)”. Na ten moment proponuję pozostawić ją wyłączoną, ponieważ przygotowaniem polityk Postgres i zabezpieczeń zajmiemy się innym razem.



Kodowanie
Nareszcie możemy przejść do części, na którą czekali wszyscy (a przynajmniej ja), czyli pisania kodu!
Nie tracąc czasu, otwórz swój ulubiony edytor kodu i zaczynamy!
Teraz możesz wybrać jedną z dwóch ścieżek: podstawową lub zaawansowaną.
Ścieżka podstawowa
Jeżeli chcesz na potrzeby nauki korzystania z Supabase dodatkowo napisać prostą aplikację Todo, przejdź dalej do ścieżki zaawansowanej.
Jeśli jednak nie czujesz takiej potrzeby, gotową aplikację znajdziesz tutaj.
Ścieżka zaawansowana
Jeżeli jednak masz trochę więcej czasu, to zachęcam Cię do samodzielnego pisania kodu, a następnie przejścia do sekcji integracji z Supabase.
Omówmy zatem, co znajduje się w przykładowej aplikacji Todo. Składa się ona z następujących komponentów:
- Todo
- TodoList
- App
- Login
Póki co ich funkcja jest czysto prezentacyjna, dlatego nie ma w nich nic specjalnego.
Ważniejszy jest natomiast plik supabaseClient.ts. To on pozwoli nam na połączenie się z naszym API poprzez utworzony obiekt klienta. W katalogu głównym możesz zauważyć również plik .env, w którym przechowywany będzie URL do naszego API, a także klucz publiczny.
Zanim zaczniemy, z panelu ustawień Supabase (ikona koła zębatego po lewej stronie) wybierz zakładkę API i skopiuj swój adres URL i klucz publiczny.
import { createClient } from '@supabase/supabase-js'
const supabaseUrl:string = process.env.REACT_APP_SUPABASE_URL!
const supabaseAnonKey:string = process.env.REACT_APP_SUPABASE_ANON_KEY!
export const supabase = createClient(supabaseUrl, supabaseAnonKey)

npm start
Logowanie
Na początku zajmijmy się panelem logowania. W pliku Login.tsx mamy wszystko, co będzie nam potrzebne, aby zalogować się do aplikacji za pomocą „magicznego linku”, czyli po prostu łącza, które wysłane zostanie na wskazany przez nas adres email w panelu logowania.
Dodajmy zatem obsługę wysłania formularza. Wewnątrz funkcji handleLogin zastąpmy wywołanie alerta tak, aby uzyskać:
const handleLogin = async (e) => {
e.preventDefault()
try {
setLoading(true)
const { error } = await supabase.auth.signIn({ email })
if (error) throw error
alert('Check your email for the login link!')
} catch (error:any) {
alert(error.error_description || error.message)
} finally {
setLoading(false)
}
}
- Jeżeli klikniesz teraz na przycisk, zobaczysz inny komunikat.

2. Wprowadź swój adres email i kliknij ponownie przycisk, po chwili powinieneś zobaczyć następujący alert:

Jeżeli sprawdzisz swoją skrzynkę pocztową, powinieneś zobaczyć maila z prośbą o potwierdzenie. Po kliknięciu w link zostaniesz przekierowany na swoję stronę. Możesz także sprawdzić, w zakładce Authentication w panelu Supabase, że twój mail pojawił się w tabeli. Oznacza to, że zostałeś zarejestrowany!
Mamy już działającą możliwość zalogowania się do naszej aplikacji, jednakże po przekierowaniu z magicznego linku nic się nie dzieje i znów widzimy panel logowania. Musimy zatem dodać logikę, która pozwoli nam sprawdzić, czy użytkownik wchodzący na stronę jest zalogowany.
Supabase posługuje się JWT (JSON Web Token) w celu uwierzytelnienia użytkownika. Token ten jest dodawany do każdego zapytania wysłanego do API i pozwala sprawdzić po stronie serwera, czy osoba ma dostęp do oczekiwanego zasobu.
Jeżeli chcesz zobaczyć, jak wygląda wygenerowany token, w narzędziach deweloperskich swojej przeglądarki wejdź w zakładkę Aplikacja > Pamięć lokalna > localhost i znajdź rekord supabase.auth.token.
W pliku App.tsx potrzebujemy stanu, który pozwoli nam przechowywać aktualną sesję, a także funkcji, która pozwoli zaktualizować ten stan przy pierwszym uruchomieniu aplikacji. Skorzystamy zatem z dwóch podstawowych hook’ów oferowanych przez React’a, czyli useState oraz useEffect. Dodajmy zatem następujący kod:
const [session, setSession] = useState<Session | null>(null)
useEffect(() => {
setSession(supabase.auth.session())
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
});
}, []);
W ten sposób, podczas pierwszego renderowania naszej aplikacji zapisana zostanie aktualna sesja. o ile użytkownik jest zalogowany. Następnie, wykorzystując dobrodziejstwo Supabase, w prosty sposób dodajemy listenner, który uważać będzie na zmiany stanu autoryzacji.
Dzięki temu, w przypadku np. wygaśnięcia tokenu, automatycznie zaktualizowana zostanie zmienna stanu przechowująca sesję.
Ostatnim krokiem jest dodanie logiki, która w zależności od tego, czy użytkownik jest zalogowany, pokaże mu panel logowania lub listę jego zadań. Wewnątrz metody render dodajemy zatem:
{!session ? <Login /> : <TodoList/>}
Wyświetlanie zadań
Możemy się już zalogować, ale póki co widzimy po prostu szablonowe zadania, które nie do końca nas przekonują. Dodajmy zatem teraz możliwość oglądania własnych zadań.
Jeżeli w panelu Supabase przejdziesz do zakładki API, zobaczysz coś, co jest jedną z największych zalet oferowanych przez ten serwis – automatyczna dokumentacja API! To właśnie dzięki temu wdrożenie Supabase do swojego projektu jest szybkie i bezbolesne, nawet bez zaawansowanej znajomości JavaScript’a.
Wykorzystajmy zatem to dobrodziejstwo i przygotujmy funkcję, która pobierze listę naszych zadań, a następnie wyświetli je w formie komponentów Task. Nasza lista powinna być pobierana przy pierwszym renderowaniu listy zadań, dlatego w komponencie TaskList dodajmy hook useEfect.
W jego wnętrzu umieśćmy kod funkcji pozwalającej na pobieranie listy zadań, który znajdziesz w zakładce API przechodząc do Tables and Views > Todo. Kod ten powinien wyglądać następująco:
Zwróćmy jednak uwagę, że powyższe polecenie pozwala na pobranie wszystkich zadań, niezależnie od użytkownika. Aby temu zaradzić, wykorzystajmy jedną z wbudowanych metod-filtrów Supabase – eq.
Pozwala ona na wskazanie w zapytaniu jednej z kolumn naszej tabeli oraz wartości, którą powinny posiadać pobierane rekordy w danej kolumnie. Jeżeli miałeś już styczność z językiem SQL, to możesz spojrzeć na tę metodę, jak na klauzulę WHERE w zapytaniu do bazy danych z użyciem SELECT.
W naszym przypadku chcemy, aby użytkownik miał możliwość zobaczenia wyłącznie swoich własnych zadań, a na to właśnie wskazuje kolumna user_id, przechowująca id użytkownika dodającego zadanie. Nasza funkcja powinna mieć zatem postać:
let { data: Todo, error } = await supabase
.from('Todo')
.select('*')
Więcej o wbudowanych metodach Supabase pozwalających na wykonywanie bardziej skomplikowanych zapytań możesz przeczytać w dokumentacji: Używanie filtrów
const { data: Todos, error } = await supabase
.from('Todo')
.select('*')
.eq('user_id', userId)
Możesz teraz pomyśleć „no dobrze, ale skąd będziemy wiedzieć, jakie jest id zalogowanego użytkownika?”. Nie trzymając Cię w niepewności już spieszę z odpowiedzią.
Tutaj również mamy już gotowe rozwiązanie dla tego problemu. Supabase oferuje metodę pozwalającą na pobranie obiektu zalogowanego użytkownika ze wszystkimi jego danymi, w tym z jego id. Metoda ta ma postać supabase.auth.user() i zwraca obiekt klasy User, którego część z atrybutów możesz sprawdzić poniżej:
export interface User {
id: string
app_metadata: {
provider?: string
[key: string]: any
}
user_metadata: {
[key: string]: any
}
/*...*/
}
Po dodaniu pobierania id użytkownika nasza funkcja ma póki co postać:
const userId = supabase.auth.user()!.id
const { data: Todos, error } = await supabase
.from('Todo')
.select('*')
.eq('user_id', userId)
Znak „!” na końcu metody user() to post-fixowy operator Non-null assertion (nie będę nawet próbował przetłumaczyć tego na polski, żeby bardziej nie kaleczyć języka :P).
Wskazuje on kompilatorowi TypeScript, że jego operand ma wartość inną niż null i undefined. Jeżeli piszesz w JavaScripcie, nie dodawaj go tam. Po kompilacji z TS do JS jest on usuwany, więc jest on potrzebny jedynie w przypadku programowania w TS.
Wszystko wygląda świetnie! Teraz pozostało jedynie opakować nasz kod w blok try-catch oraz dodać zapisywanie pobranych danych do wewnętrznego stanu naszej aplikacji. Dodaj dwa stany – todos (typu ITodo[] – plik models.ts) oraz loading (typ boolean), a następnie dodaj odpowiednio te funkcjonalności w następujący sposób:
try {
setLoading(true);
const userId = supabase.auth.user()!.id;
const { data: Todos, error, status } = await supabase
.from('Todo')
.select('*')
.eq('user_id', userId);
if (error && status !== 406) {
throw error;
}
if (Todos) {
setTodos(Todos);
}
}
catch (error:any) {
alert(error.message);
}
finally {
setLoading(false);
}
Po umieszczeniu powyższego kodu w funkcji (asynchronicznej!) a następnie wywołaniu jej w hook’u useEffect, początek naszego komponentu powinien wyglądać następująco:
const [todos,setTodos] = useState<ITodo[]>([]);
const [loading,setLoading] = useState(true);
useEffect(() => {
getTasks();
},[]);
const getTasks = async () => {
try {
setLoading(true);
const userId = supabase.auth.user()!.id;
const { data: Todos, error, status } = await supabase
.from('Todo')
.select('*')
.eq('user_id', userId);
if (error && status !== 406) {
throw error;
}
if (Todos) {
setTodos(Todos)
}
}
catch (error:any) {
alert(error.message)
}
finally {
setLoading(false)
}
}
Teraz możemy wykorzystać pobrane dane i wyświetlić je w postaci komponentów Todo korzystając z funkcji map:
{loading ?
<div>Loading...</div> :
<ul className="todo-list">
{todos.map((todo) => <Todo/>)}
</ul>
}
Póki co nie widzimy żadnych zadań, gdyż żadnego jeszcze nie dodaliśmy. Możemy dodać zadanie bezpośrednio z panelu Supabase. Wejdź w zakładkę Authentication i skopiuj swoje id użytkownika.
Ponieważ zadanie powiązane jest z daną kategorią, musimy dodać również naszą pierwszą kategorię. W zakładce Table Editor wybierz tabelę Category i kliknij Insert row. W nowym panelu wprowadź dane dla nowej kategorii i wklej swoje id użytkownika:

W ten sam sposób dodaj pierwsze zadanie. Pamiętaj, żeby wskazać odpowiednią kategorię, a także podać swoje id użytkownika:

Jeżeli wszystko przebiegło poprawnie, powinniśmy zobaczyć dokładnie jedno zadanie. Dalej nie jest ono jednak wypełnione naszymi danymi, dlatego musimy przekazać pobrane dane do komponentu zadania. Przygotujmy zatem ten komponent:
import { FunctionComponent } from "react";
import { ITodo } from "../models";
interface TodoProps {
todo: ITodo
}
const Todo: FunctionComponent<TodoProps> = ({todo}) => {
return (
<li className="todo">
<p className="todo-date">{todo?.created_at}</p>
<p className="todo-name">{todo?.name}</p>
<p className="todo-date">Category: <span>{todo?.category_id}</span></p>
<p className="todo-desc">{todo?.description}</p>
<div className="btn-container">
<button className="btn">Delete</button>
<button className="btn">Edit</button>
</div>
</li>
);
}
export default Todo;
Przekażmy do niego obiekt todo w metodzie map wraz z podaniem id zadania jako klucz:
<ul className="todo-list">
{todos.map((todo) => <Todo key={todo.id} todo={todo}/>)}
</ul>
W naszej aplikacji już widać dodane wcześniej zadanie!

Ostatnim elementem wymagającym zmiany w komponencie Todo jest poprawienie sposobu wyświetlania kategorii. Aktualnie pokazujemy jedynie jej id, a chcielibyśmy, żeby użytkownik miał możliwość zobaczenia jej nazwy.
Wyświetlanie kategorii
Aby to zrobić musimy zmodyfikować naszą funkcję pobierającą zadania, a dokładniej jej metodę select(). Aby odwołać się w niej do danych z innej, powiązanej relacją tabeli oraz pobrać je, należy podać jej nazwę oraz wskazać w nawiasach, jaka dokładnie kolumna nas interesuje. Składnia tego polecenia po zmianach powinna zatem wyglądać następująco:
.select(
`*,
Category (
name
)
`)
Jeśli wyświetlisz w konsoli pobraną listę zadań zobaczysz, że teraz nasz obiekt posiada pole Category zawierające obiekt z polem name.

Poprawmy zatem nasz komponent Todo oraz, jeżeli korzystasz z mojego szablonu lub piszesz w TypeScript’cie, dodajmy pole Category do interfejsu przygotowanego dla naszych zadań (w szablonie wszystkie interfejsy dla obiektów przygotowane są w pliku models.ts)
<p className="todo-date">Category: <span>{todo?.Category.name}</span></p>
export interface ITodo {
id: number,
name: string,
created_at?: string,
description?: string,
category_id: number,
user_id: number,
Category: {
name:string
}
}
Teraz nasze zadanie wygląda tak jak powinno.

Dodawanie zadań
Kolejną funkcjonalnością naszej aplikacji jest dodawanie zadań. W komponencie TodoList mamy już przygotowany do tego formularz, ale musimy jeszcze zapewnić jego implementację i powiązanie z naszą bazą.
Aby dodawać rekordy do bazy Supabase zapewnia metodę insert. Możesz zobaczyć sposób jej użycia wchodząc w zakładkę API i wybierając tabelę Todo. Korzystając z tej wiedzy, a także poprzedniej funkcji pobierającej zadania napiszmy funkcję dodająca zadanie do bazy:
const saveTask = async () => {
try {
const userId = supabase.auth.user()!.id;
const { error } = await supabase
.from('Todo')
.insert([{
name: taskName,
description: taskDescription,
category_id: taskCategoryId,
user_id: userId
}]);
if (error) {
throw error;
}
alert('Task successfully added')
}
catch (error:any) {
alert(error.message)
}
}
Teraz musimy przygotować stany dla poszczególnych pól formularza oraz połączyć je z nimi. Mamy 3 pola, zatem potrzebne są nam 3 stany:
const [taskName,setTaskName] = useState('');
const [taskCategoryId,setTaskCategoryId] = useState('');
const [taskDescription,setTaskDescription] = useState('');
Po podłączeniu pól dodajmy jeszcze naszą metodę saveTask, do funkcji obsługującej wysłanie formularza handleSubmit.
const handleSubmit = (e:FormEvent) => {
e.preventDefault()
if(taskCategoryId === ''){
alert('You have to choose some category')
return;
}
saveTask()
}
Wszystko byłoby idealnie, gdyby nie fakt, że dalej nie mamy możliwości wybrania kategorii dla naszego zadania, gdyż nie pobieramy jeszcze z bazy żadnej z nich. Aby się tym zająć, analogicznie do metody pobierającej zadania napiszmy metodę do pobierania kategorii i dodajmy jej wywołanie w hook’u useEffect.
const getCategories =async () => {
try {
const userId = supabase.auth.user()!.id;
const { data: Categories, error, status } = await supabase
.from('Category')
.select(`*`)
.eq('user_id', userId);
if (error && status !== 406) {
throw error;
}
if (Categories) {
setCategories(Categories);
}
}
catch (error:any) {
alert(error.message)
}
}
Mając już nasze dane, możemy zaktualizować pole select formularza tak, aby wyświetlało ono wszystkie pobrane kategorie.
<select
name="task-category"
id="task-category"
value={taskCategoryId}
onChange={(e) => setTaskCategoryId(e.target.value)}
>
<option value="">--Please choose an option--</option>
{categories.map((category) =>{
return (
<option
key={category.id}
value={category.id}>
{category.name}
</option>
)})}
</select>
Przetestujmy zatem dodanie nowego zadania. Po wysłaniu formularza, w panelu Supabase możemy sprawdzić, że pojawił się nowy rekord w tabeli Todo:

Na koniec, dla lepszego efektu, dodajmy jeszcze czyszczenie pól formularza po jego wysłaniu poprzez ustawienie ich wartości na puste stringi w metodzie saveTask.
Edycja i usuwanie zadań
/*...*/
alert('Task successfully added');
setTaskCategoryId('');
setTaskDescription('');
setTaskName('');
Sporo pracy za nami, ale została nam jeszcze jedna funkcjonalność. Musimy umożliwić użytkownikowi edycję oraz usuwanie zadań. Zajmijmy się najpierw tym drugim. Dodajmy następującą metodę do naszego komponentu TodoList:
const deleteTask = async (taskId:number) => {
try {
const userId = supabase.auth.user()!.id;
const { error } = await supabase
.from('Todo')
.delete()
.eq('id', taskId)
.eq('user_id',userId);
if (error) {
throw error;
}
alert('Task successfully deleted');
}
catch (error:any) {
alert(error.message);
}
}
Metoda ta korzysta z udostępnionej przez Supabase metody delete, która pozwala na usuwanie rekordów z bazy danych. Zauważ, że filtr eq został wykorzystany dwa razy. Łączenie filtrów jest możliwe i pozwala na tworzenie znacznie bardziej skomplikowanej logiki podczas wykonywania operacji na bazie danych.
Musimy jeszcze przekazać tę metodę do naszego komponentu Todo, a także dodać obsługę kliknięcia przycisku „Delete”. Tym sposobem nasz komponent Todo wygląda następująco:
interface TodoProps {
todo: ITodo,
deleteTask: (taskId: number) => void
}
const Todo: FunctionComponent<TodoProps> = ({todo,deleteTask}) => {
return (
<li className="todo">
<p className="todo-date">{todo?.created_at}</p>
<p className="todo-name">{todo?.name}</p>
<p className="todo-date">Category: <span>{todo?.Category.name}</span></p>
<p className="todo-desc">{todo?.description}</p>
<div className="btn-container">
<button className="btn"
onClick={() => deleteTask(todo.id)}
>
Delete
</button>
<button className="btn">Edit</button>
</div>
</li>
);
}
export default Todo;
Ostatnią rzeczą jest obsługa edycji naszych zadań. Wykorzystamy do tego zadania posiadany formularz dodawania zadań, jednak aby to zrobić potrzebujemy stanu, który pozwoli określić czy dane przechowywane w jego polach dotyczą zadania już istniejącego, czy może raczej nowego zadania. Stan ten będzie jednocześnie przechowywał informację o id zadania, które edytujemy:
const [editedTaskId,setEditedTaskId] = useState<number | null>(null);
W metodzie handleSubmit wykorzystajmy go do warunkowego wybierania odpowiednio funkcji edycji lub funkcji dodania zadania:
const handleSubmit = (e:FormEvent) => {
e.preventDefault();
if(taskCategoryId === ''){
alert('You have to choose some category');
return;
}
if(editedTaskId)
editTask(editedTaskId);
else
saveTask();
}
Korzystając dalej z dokumentacji API na Supabase napiszmy odpowiednią metodę, która będzie analogiczna do metody saveTask, ale wykorzystywać będzie metodę update zapewnioną przez Supabase. Metoda ta pozwoli na zapisanie zmian w naszym zadaniu.
const editTask = async (taskId:number) => {
try {
const userId = supabase.auth.user()!.id;
const { error } = await supabase
.from('Todo')
.update({
name: taskName,
description: taskDescription,
category_id: taskCategoryId,
})
.eq('id', taskId)
.eq('user_id',userId);
if (error) {
throw error;
}
alert('Task successfully edited');
setTaskCategoryId('');
setTaskDescription('');
setTaskName('');
setEditedTaskId(null);
}
catch (error:any) {
alert(error.message);
}
}
Więcej o metodzie delete możesz przeczytać tutaj.Teraz potrzebujemy jeszcze możliwości wskazania edytowanego zadania poprzez kliknięcie przycisku Edit danego zadania, a także możliwości umieszczenia danych w polach formularza. Napiszmy metodę handleEdit, którą umieścimy w komponencie TodoList.
const handleEdit = (taskId:number) => {
setEditedTaskId(taskId);
const editedTask = todos.find((todo) => todo.id == taskId)!
setTaskCategoryId(editedTask.category_id.toString())
setTaskDescription(editedTask.description ? editedTask.description : '')
setTaskName(editedTask.name)
}
Przekażmy tę metodę do komponentu Todo i wywołajmy ją w przypadku kliknięcia w przycisk Edit:
<button className="btn" onClick={() => handleEdit(todo.id)}>Edit</button>
Możesz teraz już przetestować edytowanie zadań.
Ostatnie szlify
Wszystko działa już jak należy, ale możliwe, że zwróciłeś uwagę na fakt, że po edycji/usunięciu zadania, lista zadań nie aktualizuje się. Musimy poprawić ten fakt tak, aby wszystko działo się dynamicznie. Możesz pomyśleć, że można po prostu wywołać zapytanie pobierające listę zadań za każdym razem, gdy następuje jakaś zmiana.
To rozwiązanie dałoby poprawny rezultat, jednak w przypadku, gdybyśmy mieli miliony rekordów, byłoby to bardzo nieoptymalne. Dlatego też lepiej jest pracować bezpośrednio na liście, którą już pobraliśmy przy pierwszym renderowaniu komponentu. Poprawmy zatem nasze metody editTask, deleteTask oraz saveTask tak, aby aktualizowały one zmienną stanu todos w przypadku poprawnego wykonania.
- editTask
setTodos(todos.map(todo =>
todo.id === taskId ?
{...todo,
name: taskName,
category_id: parseInt(taskCategoryId),
description: taskDescription,
Category: {
name: categories.find(category =>
category.id === parseInt(taskCategoryId))?.name!
}} :
todo))
- deleteTask
setTodos(todos.filter((todo) => todo.id !== taskId))
- saveTask
const newTodo:ITodo = {...Todo[0],
Category: {
name: categories.find(category =>
category.id === parseInt(taskCategoryId))?.name!
}
}
setTodos([...todos,newTodo])
Mamy już w pełni funkcjonalną, dynamiczną aplikację. Skoro zbliżamy się do końca tego artykułu, dobrze byłoby móc wylogować się z aplikacji po zakończonej pracy. Dodajmy zatem metodę, która to umożliwi. W tym przypadku również mamy praktycznie gotowe rozwiązanie dla tego problemu, dzięki metodzie supabase.auth.signOut() zapewnianej przez Supabase.
const logOut = async () => {
try{
const { error } = await supabase.auth.signOut();
if(error)
throw error;
}
catch(error){
alert('This action is unavailable right now');
}
}
Dodajmy tę metodę do obsługi kliknięcia przycisku z nagłówka naszej aplikacji oraz zapewnijmy, że przycisk będzie widoczny tylko dla zalogowanych użytkowników:
{session && <button className='btn' onClick={logOut}>Log out</button>}
Trochę samodzielnej pracy
Możliwe, że zauważyłeś, że w projekcie nie ma komponentu umożliwiającego dodawanie nowych typów zadań z poziomu aplikacji. Jest to zabieg celowy (wcale nie chodzi o moje lenistwo, przysięgam), żeby dać Ci możliwość sprawdzenia swoich sił w samodzielnej pracy z Supabase.
Skorzystaj z wiedzy zawartej w tym poradniku, a także z automatycznej dokumentacji z panelu Supabase i spróbuj przygotować tę funkcjonalność samodzielnie :). Jeżeli chcesz, możesz dodać nowy formularz w komponencie z listą zadań, albo stworzyć zupełnie nowy komponent, specjalnie dla tej funkcjonalności — możliwości jest mnóstwo!
Podsumowanie
Adam Lipiński
Za dnia początkujący front-end developer, w nocy zaś student (czasami na odwrót). Fan testowania nowych technologii oraz CSSowych sztuczek.
Search
Baza wiedzy
Ostatnie komentarze
Warto sprawdzić
Wymieńmy się wiedzą
Chcesz pracować w zespole, którego członkowie dzielą się ze sobą swoim doświadczeniem i wiedza? Pracuj z nami!