import Layout from '~/layouts/DefaultGuideLayout' export const meta = { title: 'Build a User Management App with React', description: 'Learn how to use Supabase in your React App.', } ![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). ## Building the App Let's start building the React app from scratch. ### Initialize a React app We can use [Create React App](https://create-react-app.dev/docs/getting-started/) to initialize an app called `supabase-react`: ```bash npx create-react-app supabase-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). ```bash title=.env REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL REACT_APP_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`: ```js title=src/supabaseClient.js import { createClient } from '@supabase/supabase-js' const supabaseUrl = process.env.REACT_APP_SUPABASE_URL const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey) ``` And one 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.js`: ```jsx title=src/Auth.js import { useState } from 'react' import { supabase } from './supabaseClient' export default function Auth() { const [loading, setLoading] = useState(false) const [email, setEmail] = useState('') const handleLogin = async (e) => { e.preventDefault() try { setLoading(true) const { error } = await supabase.auth.signInWithOtp({ email }) if (error) throw error alert('Check your email for the login link!') } catch (error) { alert(error.error_description || error.message) } finally { setLoading(false) } } return (

Supabase + React

Sign in via magic link with your email below

{loading ? ( 'Sending magic link...' ) : (
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.js`. ```jsx title=src/Account.js import { useState, useEffect } from 'react' import { supabase } from './supabaseClient' const Account = ({ session }) => { const [loading, setLoading] = useState(true) const [username, setUsername] = useState(null) const [website, setWebsite] = useState(null) const [avatar_url, setAvatarUrl] = useState(null) useEffect(() => { getProfile() }, [session]) const getProfile = async () => { try { setLoading(true) const { user } = session let { data, error, status } = await supabase .from('profiles') .select(`username, website, avatar_url`) .eq('id', user.id) .single() if (error && status !== 406) { throw error } if (data) { setUsername(data.username) setWebsite(data.website) setAvatarUrl(data.avatar_url) } } catch (error) { alert(error.message) } finally { setLoading(false) } } const updateProfile = async (e) => { e.preventDefault() try { setLoading(true) const { user } = session const updates = { id: user.id, username, website, avatar_url, updated_at: new Date(), } let { error } = await supabase.from('profiles').upsert(updates) if (error) { throw error } } catch (error) { alert(error.message) } finally { setLoading(false) } } return (
{loading ? ( 'Saving ...' ) : (
Email: {session.user.email}
setUsername(e.target.value)} />
setWebsite(e.target.value)} />
)}
) } export default Account ``` ### Launch! Now that we have all the components in place, let's update `src/App.js`: ```jsx title=src/App.js import './index.css' import { useState, useEffect } from 'react' import { supabase } from './supabaseClient' import Auth from './Auth' import Account from './Account' export default 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 ? : }
) } ``` Once that's done, run this in a terminal window: ```bash npm start ``` And then open the browser to [localhost:3000](http://localhost:3000) 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.js`: ```jsx title=src/Avatar.js 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]) const downloadImage = async (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) } } const uploadAvatar = async (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}` let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file) if (uploadError) { throw uploadError } onUpload(filePath) } catch (error) { alert(error.message) } finally { setUploading(false) } } return (
{avatarUrl {uploading ? ( 'Uploading...' ) : ( <>
)}
) } ``` ### Add the new widget And then we can add the widget to the Account page at `src/Account.js`: ```jsx title=src/Account.js // Import the new component import Avatar from './Avatar' // ... return (
{/* Add to the body */} { setAvatarUrl(url) updateProfile({ username, website, avatar_url: url }) }} /> {/* ... */} ) ``` ### Storage management At this stage you have a fully functional application! export const Page = ({ children }) => export default Page