--- title: 'Build a User Management App with React' description: 'Learn how to use Supabase in your React App.' --- <$Partial path="quickstart_intro.mdx" /> ![Supabase User Management example](/docs/img/user-management-demo.png) If you get stuck while working through this guide, refer to the [full example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/react-user-management). <$Partial path="project_setup.mdx" /> ## Building the app Let's start building the React app from scratch. ### Initialize a React app We can use [Vite](https://vitejs.dev/guide/) to initialize an app called `supabase-react`: ```bash npm create vite@latest supabase-react -- --template react cd supabase-react ``` Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js). ```bash npm install @supabase/supabase-js ``` And finally we want to save the environment variables in a `.env.local` file. All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys). <$CodeTabs> ```bash name=.env VITE_SUPABASE_URL=YOUR_SUPABASE_URL VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ``` Now that we have the API credentials in place, let's create a helper file to initialize the Supabase client. These variables will be exposed on the browser, and that's completely fine since we have [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. Create and edit `src/supabaseClient.js`: <$CodeTabs> ```js name=src/supabaseClient.js import { createClient } from '@supabase/supabase-js' const supabaseUrl = import.meta.env.VITE_SUPABASE_URL const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey) ``` ### App styling (optional) An optional step is to update the CSS file `src/index.css` to make the app look nice. You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/react-user-management/src/index.css). ### Set up a login component Let's set up a React component to manage logins and sign ups. We'll use Magic Links, so users can sign in with their email without using passwords. Create and edit `src/Auth.jsx`: <$CodeTabs> ```jsx name=src/Auth.jsx import { useState } from 'react' import { supabase } from './supabaseClient' export default function Auth() { const [loading, setLoading] = useState(false) const [email, setEmail] = useState('') const handleLogin = async (event) => { event.preventDefault() setLoading(true) const { error } = await supabase.auth.signInWithOtp({ email }) if (error) { alert(error.error_description || error.message) } else { alert('Check your email for the login link!') } setLoading(false) } return (

Supabase + React

Sign in via magic link with your email below

setEmail(e.target.value)} />
) } ``` ### Account page After a user is signed in we can allow them to edit their profile details and manage their account. Let's create a new component for that called `src/Account.jsx`. <$CodeTabs> ```jsx name=src/Account.jsx import { useState, useEffect } from 'react' import { supabase } from './supabaseClient' export default function Account({ session }) { const [loading, setLoading] = useState(true) const [username, setUsername] = useState(null) const [website, setWebsite] = useState(null) const [avatar_url, setAvatarUrl] = useState(null) useEffect(() => { let ignore = false async function getProfile() { setLoading(true) const { user } = session const { data, error } = await supabase .from('profiles') .select(`username, website, avatar_url`) .eq('id', user.id) .single() if (!ignore) { if (error) { console.warn(error) } else if (data) { setUsername(data.username) setWebsite(data.website) setAvatarUrl(data.avatar_url) } } setLoading(false) } getProfile() return () => { ignore = true } }, [session]) async function updateProfile(event, avatarUrl) { event.preventDefault() setLoading(true) const { user } = session const updates = { id: user.id, username, website, avatar_url: avatarUrl, updated_at: new Date(), } const { error } = await supabase.from('profiles').upsert(updates) if (error) { alert(error.message) } else { setAvatarUrl(avatarUrl) } setLoading(false) } return (
setUsername(e.target.value)} />
setWebsite(e.target.value)} />
) } ``` ### Launch! Now that we have all the components in place, let's update `src/App.jsx`: <$CodeTabs> ```jsx name=src/App.jsx import './App.css' import { useState, useEffect } from 'react' import { supabase } from './supabaseClient' import Auth from './Auth' import Account from './Account' function App() { const [session, setSession] = useState(null) useEffect(() => { supabase.auth.getSession().then(({ data: { session } }) => { setSession(session) }) supabase.auth.onAuthStateChange((_event, session) => { setSession(session) }) }, []) return (
{!session ? : }
) } export default App ``` Once that's done, run this in a terminal window: ```bash npm run dev ``` And then open the browser to [localhost:5173](http://localhost:5173) and you should see the completed app. ![Supabase React](/docs/img/supabase-react-demo.png) ## Bonus: Profile photos Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like photos and videos. ### Create an upload widget Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component: Create and edit `src/Avatar.jsx`: <$CodeTabs> ```jsx name=src/Avatar.jsx import { useEffect, useState } from 'react' import { supabase } from './supabaseClient' export default function Avatar({ url, size, onUpload }) { const [avatarUrl, setAvatarUrl] = useState(null) const [uploading, setUploading] = useState(false) useEffect(() => { if (url) downloadImage(url) }, [url]) async function downloadImage(path) { try { const { data, error } = await supabase.storage.from('avatars').download(path) if (error) { throw error } const url = URL.createObjectURL(data) setAvatarUrl(url) } catch (error) { console.log('Error downloading image: ', error.message) } } async function uploadAvatar(event) { try { setUploading(true) if (!event.target.files || event.target.files.length === 0) { throw new Error('You must select an image to upload.') } const file = event.target.files[0] const fileExt = file.name.split('.').pop() const fileName = `${Math.random()}.${fileExt}` const filePath = `${fileName}` const { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file) if (uploadError) { throw uploadError } onUpload(event, filePath) } catch (error) { alert(error.message) } finally { setUploading(false) } } return (
{avatarUrl ? ( Avatar ) : (
)}
) } ``` ### Add the new widget And then we can add the widget to the Account page at `src/Account.jsx`: <$CodeTabs> ```jsx name=src/Account.jsx // Import the new component import Avatar from './Avatar' // ... return (
{/* Add to the body */} { updateProfile(event, url) }} /> {/* ... */} ) ``` At this stage you have a fully functional application!