Real-time Table Changes in Supabase with React.js/Next.js

Real-time applications still amaze me.

The ability to instantly reflect changes in a database to all connected clients makes things feel sophisticated. Thankfully, Supabase makes this easy to set up.

In this article, I'll show you how to subscribe to real-time changes in a Supabase table using Next.js (assuming you already have a Next.js app and Supabase set up).

Enabling Real-time for Your Table

Before diving into the code, you must enable real-time functionality for your specific table in the Supabase dashboard.

I have forgotten this step a few times and spent too long wondering why my subscriptions weren't working.

  1. Log in to your Supabase dashboard
  2. Navigate to your project
  3. Go to Database -> Tables
  4. Find the table you want to enable real-time for (in our case, 'todos')
  5. Click on the three dots next to the table name
  6. Select "Edit table"
  7. In the "Enable Realtime" section, turn on the toggle for "Enable Realtime for this table"
  8. Save your changes

Again, our real-time subscriptions won't work without this step, even if your code is correct.

Subscribing to Real-time Changes

Let's create a component that subscribes to real-time changes in our Supabase 'todos' table.

Create a new file named components/TodoList.js:

import { useState, useEffect, useCallback } from 'react'
import { supabase } from '../lib/supabaseClient'

export default function TodoList() {
  const [todos, setTodos] = useState([])

  const fetchTodos = useCallback(async () => {
    const { data, error } = await supabase
      .from('todos')
      .select('*')
      .order('id', { ascending: true })
    
    if (error) console.error('Error fetching todos:', error)
    else setTodos(data)
  }, [])

  useEffect(() => {
    // Fetch initial todos
    fetchTodos()

    // Set up real-time subscription
    const channel = supabase
      .channel('custom-all-channel')
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table: 'todos' },
        (payload) => {
          console.log('Change received!', payload)
          
          if (payload.eventType === 'INSERT') {
            setTodos(prevTodos => [...prevTodos, payload.new])
          } else if (payload.eventType === 'UPDATE') {
            setTodos(prevTodos => prevTodos.map(todo => 
              todo.id === payload.new.id ? payload.new : todo
            ))
          } else if (payload.eventType === 'DELETE') {
            setTodos(prevTodos => prevTodos.filter(todo => todo.id !== payload.old.id))
          }
        }
      )
      .subscribe()

    // Cleanup subscription on component unmount
    return () => {
      channel.unsubscribe()
    }
  }, [fetchTodos])

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.task} - {todo.is_completed ? 'Completed' : 'Pending'}
        </li>
      ))}
    </ul>
  )
}

Let's walk through this snippet:

I'm using useCallback for the fetchTodos function. This prevents unnecessary re-renders and ensures the function's reference stability across renders.

In the useEffect hook, we set up our real-time subscription when the component mounts. We also clean up the subscription when the component unmounts to prevent memory leaks.

We're using supabase.channel() to create a new real-time channel. This is more efficient than the older supabase.from('todos').on() method, allowing for more granular control and better performance.

We're listening for all events ('*') on the 'todos' table. You can optimize this by specifying only the needed events (e.g., 'INSERT', 'UPDATE', 'DELETE').

When a change is received, we update the state directly in the subscription callback. This is more efficient than calling a separate function, as it reduces the number of renders.

ReactjsNextjsSupabase
Avatar for Niall Maher

Written by Niall Maher

Founder of Codú - The web developer community! I've worked in nearly every corner of technology businesses: Lead Developer, Software Architect, Product Manager, CTO, and now happily a Founder.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.