Supabase with React
Learn how to integrate Supabase into your React applications following best practices for authentication, data fetching, and state management.
βοΈ React Integration
- Client Setup: Configure Supabase client for React
- Auth Context: Share auth state across components
- Custom Hooks: Reusable data fetching patterns
- Optimistic Updates: Smooth user experience
Project Setup
# Create React app
npx create-react-app my-app
# or with Vite
npm create vite@latest my-app -- --template react-ts
# Install Supabase
npm install @supabase/supabase-js
Create Supabase Client
// src/lib/supabase.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseKey)
Auth Context Provider
// src/contexts/AuthContext.jsx
import { createContext, useContext, useEffect, useState } from 'react'
import { supabase } from '../lib/supabase'
const AuthContext = createContext({})
export function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
setLoading(false)
})
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setUser(session?.user ?? null)
}
)
return () => subscription.unsubscribe()
}, [])
const signIn = (email, password) =>
supabase.auth.signInWithPassword({ email, password })
const signUp = (email, password) =>
supabase.auth.signUp({ email, password })
const signOut = () => supabase.auth.signOut()
return (
<AuthContext.Provider value={{ user, loading, signIn, signUp, signOut }}>
{children}
</AuthContext.Provider>
)
}
export const useAuth = () => useContext(AuthContext)
Protected Routes
// src/components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
export function ProtectedRoute({ children }) {
const { user, loading } = useAuth()
if (loading) return <div>Loading...</div>
if (!user) return <Navigate to="/login" />
return children
}
Data Fetching Hook
// src/hooks/usePosts.js
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
export function usePosts() {
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetchPosts()
}, [])
async function fetchPosts() {
try {
setLoading(true)
const { data, error } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false })
if (error) throw error
setPosts(data)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
async function createPost(title, content) {
const { data, error } = await supabase
.from('posts')
.insert({ title, content })
.select()
.single()
if (!error) setPosts([data, ...posts])
return { data, error }
}
return { posts, loading, error, createPost, refetch: fetchPosts }
}
Realtime Hook
// src/hooks/useRealtimePosts.js
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
export function useRealtimePosts() {
const [posts, setPosts] = useState([])
useEffect(() => {
// Fetch initial data
supabase.from('posts').select('*').then(({ data }) => {
setPosts(data || [])
})
// Subscribe to changes
const channel = supabase
.channel('posts')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'posts' },
(payload) => {
if (payload.eventType === 'INSERT') {
setPosts(prev => [payload.new, ...prev])
} else if (payload.eventType === 'DELETE') {
setPosts(prev => prev.filter(p => p.id !== payload.old.id))
} else if (payload.eventType === 'UPDATE') {
setPosts(prev => prev.map(p =>
p.id === payload.new.id ? payload.new : p
))
}
}
)
.subscribe()
return () => supabase.removeChannel(channel)
}, [])
return posts
}
Login Component
// src/components/Login.jsx
import { useState } from 'react'
import { useAuth } from '../contexts/AuthContext'
export function Login() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const { signIn } = useAuth()
const handleSubmit = async (e) => {
e.preventDefault()
setError(null)
const { error } = await signIn(email, password)
if (error) setError(error.message)
}
return (
<form onSubmit={handleSubmit}>
{error && <p className="text-red-500">{error}</p>}
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Sign In</button>
</form>
)
}
π‘ Key Takeaways
- β’ Use AuthContext to share auth state across components
- β’ Create custom hooks for data fetching patterns
- β’ Clean up realtime subscriptions on unmount
- β’ Protect routes based on authentication state
π Learn More
-
React Quickstart β
Official Supabase React quickstart guide.
-
Next.js Auth Helpers β
For Next.js projects, use the official auth helpers.