🐈
Supabase × Next.js TodoAPP 学習メモ
Supabase(オープンソースのBaaS)のExample projectsのメモ
これは学習メモです!
基本的にはREADME.mdを読めば良い。
必要なコマンドなどまとめておきます。
Exampleprojectsのソースコード
Supabase の JavaScript ⽤クライアントインストール
yarn add @supabase/supabase-js
Supabase UI のインストール
yarn add @supabase/ui
使う部品は随時importする!
(例)Authを使う
import { Auth } from '@supabase/ui'
公式のコードサンプル
import { Auth, Typography, Button } from '@supabase/ui'
import { createClient } from '@supabase/supabase-js'
<!-- 'PROJECT_ANON_KEY'は.env.localなどで設定 -->
const supabase = createClient('https://your-project-url.supabase.co', 'PROJECT_ANON_KEY')
const Container = (props) => {
const { user } = Auth.useUser()
if (user)
return (
<>
<Typography.Text>Signed in: {user.email}</Typography.Text>
<Button block onClick={() => props.supabaseClient.auth.signOut()}>
Sign out
</Button>
</>
)
return props.children
}
export default function AuthBasic() {
return (
<Auth.UserContextProvider supabaseClient={supabase}>
<Container supabaseClient={supabase}>
<Auth supabaseClient={supabase} />
</Container>
</Auth.UserContextProvider>
)
}
TailwindCSS のインストール
yarn add tailwindcss
yarn add postcss-flexbugs-fixes@4.2.1
yarn add postcss-preset-env@6.7.0
TailwindCSSはこのあたりを見ながら、設定する。
ファイルの内容をひもとく・・・
index.js
ログイン画面
SupabaseUIのAuthを使ってる。
import { supabase } from '../lib/initSupabase'
import { Auth } from '@supabase/ui'
import TodoList from '../components/TodoList'
export default function IndexPage() {
const { user } = Auth.useUser()
return (
<div className="w-full h-full bg-gray-300">
<!-- ユーザーがいなかったら -->
{!user ? (
<div className="w-full h-full flex justify-center items-center p-4">
<Auth
supabaseClient={supabase}
providers={['google', 'github']}
socialLayout="horizontal"
socialButtonSize="xlarge"
/>
</div>
) : (
<!-- ユーザーがいたら -->
<div
className="w-full h-full flex flex-col justify-center items-center p-4"
style={{ minWidth: 250, maxWidth: 600, margin: 'auto' }}
>
<!-- TodoListにuser={supabase.auth.user()}をわたす -->
<TodoList user={supabase.auth.user()} />
<!-- ログアウトボタン -->
<button
className="btn-black w-full mt-12"
onClick={async () => {
const { error } = await supabase.auth.signOut()
if (error) console.log('Error logging out:', error.message)
}}
>
Logout
</button>
</div>
)}
</div>
)
}
TodoList.js
component配下にTodoList.jsを作成。
ユーザーがログインしているときのみ表示される。
<!-- 必要なものをimport -->
import { useState, useEffect } from 'react'
import { supabase } from '../lib/initSupabase'
export default function Todos({ user }) {
<!-- todo -->
const [todos, setTodos] = useState([])
<!-- 入力したtodo -->
const [newTaskText, setNewTaskText] = useState('')
const [errorText, setError] = useState('')
useEffect(() => {
fetchTodos()
}, [])
<!-- supabaseに接続 -->
const fetchTodos = async () => {
<!-- .from(table名).select('カラム名').order(ソートの条件) -->
let { data: todos, error } = await supabase.from('todos').select('*').order('id', true)
if (error) console.log('error', error)
else setTodos(todos)
}
<!-- todoの追加 -->
const addTodo = async (taskText) => {
let task = taskText.trim()
if (task.length) {
let { data: todo, error } = await supabase
.from('todos')
.insert({ task, user_id: user.id })
.single()
if (error) setError(error.message)
else setTodos([...todos, todo])
}
}
<!-- todoの削除 -->
const deleteTodo = async (id) => {
try {
await supabase.from('todos').delete().eq('id', id)
setTodos(todos.filter((x) => x.id != id))
} catch (error) {
console.log('error', error)
}
}
return (
<div className="w-full">
<h1 className="mb-12">Todo List.</h1>
<div className="flex gap-2 my-2">
<input
className="rounded w-full p-2"
type="text"
placeholder="make coffee"
value={newTaskText}
onChange={(e) => {
setError('')
setNewTaskText(e.target.value)
}}
/>
<button className="btn-black" onClick={() => addTodo(newTaskText)}>
Add
</button>
</div>
{!!errorText && <Alert text={errorText} />}
<div className="bg-white shadow overflow-hidden rounded-md">
<ul>
{todos.map((todo) => (
<Todo key={todo.id} todo={todo} onDelete={() => deleteTodo(todo.id)} />
))}
</ul>
</div>
</div>
)
}
<!-- ☑の処理 -->
const Todo = ({ todo, onDelete }) => {
const [isCompleted, setIsCompleted] = useState(todo.is_complete)
const toggle = async () => {
try {
const { data, error } = await supabase
.from('todos')
.update({ is_complete: !isCompleted })
.eq('id', todo.id)
.single()
if (error) {
throw new Error(error)
}
setIsCompleted(data.is_complete)
} catch (error) {
console.log('error', error)
}
}
return (
<li
onClick={(e) => {
e.preventDefault()
toggle()
}}
className="w-full block cursor-pointer hover:bg-gray-200 focus:outline-none focus:bg-gray-200 transition duration-150 ease-in-out"
>
<div className="flex items-center px-4 py-4 sm:px-6">
<div className="min-w-0 flex-1 flex items-center">
<div className="text-sm leading-5 font-medium truncate">{todo.task}</div>
</div>
<div>
<input
className="cursor-pointer"
onChange={(e) => toggle()}
type="checkbox"
checked={isCompleted ? true : ''}
/>
</div>
<button
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onDelete()
}}
className="w-4 h-4 ml-2 border-2 hover:border-black rounded"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="gray">
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</li>
)
}
const Alert = ({ text }) => (
<div className="rounded-md bg-red-100 p-4 my-3">
<div className="text-sm leading-5 text-red-700">{text}</div>
</div>
)
Vercelでデプロイ
.env.local に設定していた環境変数をVercelにて設定する。
Vercel のプロジェクトページにアクセス
Settings → Environment Variablesをクリック、環境変数の設定をする。
まとめ
上手くいくと、Todoリストで入力した内容がSupabaseに反映される。
公式サイトをよく読んだ。
英語の勉強にもなった。
Next.js勉強するぞ。
Discussion