TechLead
Lección 8 de 8
5 min de lectura
Supabase

Supabase con React

Integra Supabase en aplicaciones React con buenas prácticas

Supabase con React

Aprende a integrar Supabase en tus aplicaciones React siguiendo buenas prácticas para autenticación, obtención de datos y manejo de estado.

⚛️ Integración con React

  • Configuración del cliente: Prepara el cliente de Supabase para React
  • Contexto de Auth: Comparte estado de autenticación entre componentes
  • Hooks personalizados: Patrones reutilizables de fetching
  • Actualizaciones optimistas: Experiencia fluida para el usuario

Configuración del proyecto

# 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

Crear cliente de Supabase

// 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)

Proveedor de contexto de autenticación

// 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)

Rutas protegidas

// 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
}

Hook de obtención de datos

// 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 }
}

Hook de Realtime

// 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
}

Componente de inicio de sesión

// 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>
  )
}

💡 Puntos clave

  • • Usa AuthContext para compartir el estado de auth
  • • Crea hooks personalizados para patrones de fetching
  • • Limpia suscripciones realtime al desmontar
  • • Protege rutas según el estado de autenticación

📚 Más recursos

Continuar Aprendiendo