使用 React 构建用户管理应用
探索适用于你的 Supabase 应用的即用型 UI 组件。
基于 shadcn/ui 构建的 UI 组件,通过单个命令连接到 Supabase。
探索组件本教程演示如何构建一个基本的用户管理应用。该应用对用户进行身份验证和识别,将他们的个人资料信息存储在数据库中,并允许用户登录、更新他们的个人资料详细信息以及上传个人资料照片。该应用使用
- Supabase 数据库 - 一个用于存储用户数据的 Postgres 数据库,以及 行级别安全,以保护数据并确保用户只能访问他们自己的信息。
- Supabase Auth - 允许用户注册和登录。
- Supabase Storage - 允许用户上传个人资料照片。

如果在阅读本指南时遇到问题,请参考 GitHub 上的完整示例。
项目设置#
在开始构建之前,您需要设置数据库和 API。您可以通过在 Supabase 中启动一个新项目,然后在数据库中创建一个“schema”来完成此操作。
创建项目#
- 在 Supabase 控制面板中创建一个新项目。
- 输入您的项目详细信息。
- 等待新的数据库启动。
设置数据库 schema#
现在设置数据库 schema。您可以使用 SQL 编辑器中的“用户管理 Starter”快速入门,或者您可以复制/粘贴下面的 SQL 并运行它。
获取 API 详细信息#
现在您已经创建了一些数据库表,您可以使用自动生成的 API 插入数据。
为此,你需要从 项目 Connect 对话框 获取项目 URL 和密钥。
API 密钥的更改
Supabase 正在更改密钥的工作方式,以提高项目安全性和开发人员体验。您可以 阅读完整的公告,但在过渡期间,您可以使用当前的 anon 和 service_role 密钥以及新的可发布密钥,格式为 sb_publishable_xxx,它将取代旧的密钥。
在大多数情况下,你可以从 项目的 连接 对话框 获取正确的密钥,但如果你想要特定的密钥,你可以在 项目的设置页面中的 API 密钥部分 找到所有密钥
- 对于旧版密钥,从 旧版 API 密钥 选项卡中复制
anon密钥用于客户端操作,并复制service_role密钥用于服务器端操作。 - 对于新密钥,打开 API 密钥 选项卡,如果您还没有可发布密钥,请单击 创建新的 API 密钥,并复制 可发布密钥 部分中的值。
阅读 API 密钥文档 以全面了解所有密钥类型及其用途。
构建应用#
开始从头构建 React 应用。
初始化一个 React 应用#
使用 Vite 初始化一个名为 supabase-react 的应用
1npm create vite@latest supabase-react -- --template react2cd supabase-react安装唯一的附加依赖项:supabase-js。
1npm install @supabase/supabase-js最后,将环境变量保存到 .env.local 文件中。使用你之前复制的 项目 URL 和密钥。
.env
1VITE_SUPABASE_URL=2VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=API 凭据就绪后,创建一个辅助文件来初始化 Supabase 客户端。这些变量将在浏览器中公开,但这没关系,因为你已经在数据库上启用了 行级别安全。
创建并编辑 src/supabaseClient.js
src/supabaseClient.js
1/**2 * lib/supabaseClient.js3 * Helper to initialize the Supabase client.4 */56import { createClient } from '@supabase/supabase-js'78const supabaseUrl = import.meta.env.VITE_SUPABASE_URL9const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY1011export const supabase = createClient(supabaseUrl, supabasePublishableKey)应用样式 (可选)#
一个可选步骤是更新 CSS 文件 src/index.css 以使应用看起来更美观。你可以在 这里 找到此文件的完整内容。
设置登录组件#
创建一个 React 组件来管理登录和注册。它使用 Magic Links,因此用户无需使用密码即可通过电子邮件登录。
创建并编辑 src/Auth.jsx
src/Auth.jsx
1import { useState } from 'react'2import { supabase } from './supabaseClient'34export default function Auth() {5 const [loading, setLoading] = useState(false)6 const [email, setEmail] = useState('')78 const handleLogin = async (event) => {9 event.preventDefault()1011 setLoading(true)12 const { error } = await supabase.auth.signInWithOtp({ email })1314 if (error) {15 alert(error.error_description || error.message)16 } else {17 alert('Check your email for the login link!')18 }19 setLoading(false)20 }2122 return (23 <div className="row flex flex-center">24 <div className="col-6 form-widget">25 <h1 className="header">Supabase + React</h1>26 <p className="description">Sign in via magic link with your email below</p>27 <form className="form-widget" onSubmit={handleLogin}>28 <div>29 <input30 className="inputField"31 type="email"32 placeholder="Your email"33 value={email}34 required={true}35 onChange={(e) => setEmail(e.target.value)}36 />37 </div>38 <div>39 <button className={'button block'} disabled={loading}>40 {loading ? <span>Loading</span> : <span>Send magic link</span>}41 </button>42 </div>43 </form>44 </div>45 </div>46 )47}账户页面#
用户还需要一种编辑他们的个人资料详细信息并在登录后管理他们的帐户的方式。
创建一个上传小部件#
每个 Supabase 项目都配置了 存储,用于管理大型文件,如照片和视频。
为用户创建一个头像,以便他们可以上传个人资料照片。首先创建一个新组件
创建并编辑 src/Avatar.jsx
src/Avatar.jsx
1import { useEffect, useState } from 'react'2import { supabase } from './supabaseClient'34export default function Avatar({ url, size, onUpload }) {5 const [avatarUrl, setAvatarUrl] = useState(null)6 const [uploading, setUploading] = useState(false)78 useEffect(() => {9 if (url) downloadImage(url)10 }, [url])1112 async function downloadImage(path) {13 try {14 const { data, error } = await supabase.storage.from('avatars').download(path)15 if (error) {16 throw error17 }18 const url = URL.createObjectURL(data)19 setAvatarUrl(url)20 } catch (error) {21 console.log('Error downloading image: ', error.message)22 }23 }2425 async function uploadAvatar(event) {26 try {27 setUploading(true)2829 if (!event.target.files || event.target.files.length === 0) {30 throw new Error('You must select an image to upload.')31 }3233 const file = event.target.files[0]34 const fileExt = file.name.split('.').pop()35 const fileName = `${Math.random()}.${fileExt}`36 const filePath = `${fileName}`3738 let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)3940 if (uploadError) {41 throw uploadError42 }4344 onUpload(event, filePath)45 } catch (error) {46 alert(error.message)47 } finally {48 setUploading(false)49 }50 }5152 return (53 <div>54 {avatarUrl ? (55 <img56 src={avatarUrl}57 alt="Avatar"58 className="avatar image"59 style={{ height: size, width: size }}60 />61 ) : (62 <div className="avatar no-image" style={{ height: size, width: size }} />63 )}64 <div style={{ width: size }}>65 <label className="button primary block" htmlFor="single">66 {uploading ? 'Uploading ...' : 'Upload'}67 </label>68 <input69 style={{70 visibility: 'hidden',71 position: 'absolute',72 }}73 type="file"74 id="single"75 accept="image/*"76 onChange={uploadAvatar}77 disabled={uploading}78 />79 </div>80 </div>81 )82}用户登录后,允许他们编辑个人资料详细信息并管理帐户。
为此创建一个名为 src/Account.jsx 的新组件,并添加之前创建的 Avatar 组件。
src/Account.jsx
1import { useState, useEffect } from 'react'2import { supabase } from './supabaseClient'3import Avatar from './Avatar'45export default function Account({ user }) {6 const [loading, setLoading] = useState(true)7 const [username, setUsername] = useState(null)8 const [website, setWebsite] = useState(null)9 const [avatar_url, setAvatarUrl] = useState(null)1011 useEffect(() => {12 let ignore = false13 async function getProfile() {14 setLoading(true)1516 const { data, error } = await supabase17 .from('profiles')18 .select(`username, website, avatar_url`)19 .eq('id', user.id)20 .single()2122 if (!ignore) {23 if (error) {24 console.warn(error)25 } else if (data) {26 setUsername(data.username)27 setWebsite(data.website)28 setAvatarUrl(data.avatar_url)29 }30 }3132 setLoading(false)33 }3435 getProfile()3637 return () => {38 ignore = true39 }40 }, [user])4142 async function updateProfile(event, avatarUrl) {43 event.preventDefault()4445 setLoading(true)4647 const updates = {48 id: user.id,49 username,50 website,51 avatar_url: avatarUrl,52 updated_at: new Date(),53 }5455 const { error } = await supabase.from('profiles').upsert(updates)5657 if (error) {58 alert(error.message)59 } else {60 setAvatarUrl(avatarUrl)61 }62 setLoading(false)63 }6465 return (66 <form onSubmit={updateProfile} className="form-widget">67 <Avatar68 url={avatar_url}69 size={150}70 onUpload={(event, url) => {71 updateProfile(event, url)72 }}73 />74 <div>75 <label htmlFor="email">Email</label>76 <input id="email" type="text" value={user.email} disabled />77 </div>78 <div>79 <label htmlFor="username">Name</label>80 <input81 id="username"82 type="text"83 required84 value={username || ''}85 onChange={(e) => setUsername(e.target.value)}86 />87 </div>88 <div>89 <label htmlFor="website">Website</label>90 <input91 id="website"92 type="url"93 value={website || ''}94 onChange={(e) => setWebsite(e.target.value)}95 />96 </div>9798 <div>99 <button className="button block primary" type="submit" disabled={loading}>100 {loading ? 'Loading ...' : 'Update'}101 </button>102 </div>103104 <div>105 <button className="button block" type="button" onClick={() => supabase.auth.signOut()}>106 Sign Out107 </button>108 </div>109 </form>110 )111}启动!#
现在你已经准备好所有组件,请更新 src/App.jsx,它通过 getUser 方法获取当前用户,如果存在会话的话。此方法向 Supabase Auth 服务器执行网络请求。
src/App.jsx
1import { useState, useEffect } from 'react'2import './App.css'3import { supabase } from './supabaseClient'4import Auth from './Auth'5import Account from './Account'67function App() {8 const [user, setUser] = useState(null)910 useEffect(() => {11 supabase.auth.getUser().then(({ data: { user } }) => {12 setUser(user)13 })1415 supabase.auth.onAuthStateChange(async () => {16 const { data: { user } } = await supabase.auth.getUser()17 setUser(user)18 })19 }, [])2021 return (22 <div className="container" style={{ padding: '50px 0 100px 0' }}>23 {!user ? <Auth /> : <Account key={user.id} user={user} />}24 </div>25 )26}2728export default App完成此操作后,在终端窗口中运行以下命令
1npm run dev然后打开浏览器到 localhost:5173,您应该会看到完成的应用。

此时,您已经拥有一个功能齐全的应用!