🐈

Supabase × Next.js TodoAPP 学習メモ

8 min read

Supabase(オープンソースのBaaS)のExample projectsのメモ

これは学習メモです!
基本的にはREADME.mdを読めば良い。
必要なコマンドなどまとめておきます。

https://zenn.dev/uhyo/articles/technical-articles

Exampleprojectsのソースコード

https://github.com/supabase/supabase/tree/master/examples/nextjs-todo-list

Supabase の JavaScript ⽤クライアントインストール

https://supabase.io/docs/reference/javascript/installing
yarn add @supabase/supabase-js

Supabase UI のインストール

https://ui.supabase.io/
yarn add @supabase/ui

使う部品は随時importする!

(例)Authを使う

https://ui.supabase.io/components/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はこのあたりを見ながら、設定する。

https://tailwindcss.com/

https://nextjs.org/docs/advanced-features/customizing-postcss-config

https://zenn.dev/grinch1252/articles/931b2ff058ef62

https://segakuin.com/css/tailwind/

ファイルの内容をひもとく・・・

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

ログインするとコメントできます