Supabase allows us to subscribe to changes in the database, and update our UI, without the user needing to refresh the page. In this lesson, we create a subscription for postgres_changes
, listening for any change events - insert, update or delete - on our tweets table.
Additionally, we call the router.refresh()
function to re-run our Server Components, fetching fresh data from Supabase.
Subscribe to database changes
const channel = supabase
.channel("realtime tweets")
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "tweets",
},
(payload) => {
router.refresh();
}
)
.subscribe();
Instructor: Our application shows all the tweets that were available when we navigated to this page. If someone puts up a new tweet, we won't see it in our UI until we refresh. Let's subscribe to changes in the tweets table so our UI remains in sync with the database.
Over in the Supabase dashboard, we want to go to Database and then Replication. Then under supabase_realtime, we want to click 0 tables and enable replication for the tweets table. This tells Supabase to send us all updates about the tweets table, which we can then subscribe to in our tweets component.
Let's use useEffect which comes in from react. This takes a callback function that we want to run when our component mounts, so we give it an empty dependency array. Now we need a supabase client to set up this subscription. Let's declare that outside our useEffect by calling the createClientComponentClient function which comes in from our auth-helpers package.
Then inside our useEffect, we can get a channel by calling supabase.channel. The name of this one can be anything. I'm going to call it realtime tweets. We then say, on postgres_changes. The name of this one is important, needs to be all lowercase and with an underscore.
We can then pass a filter to say the event that we want to know about is *. This is, insert updates and deletes. The schema is going to be public, and the table is our tweets table. We then give it a callback function which supabase will call any time there's a change detected on the public.tweets table. It will pass in a payload which represents what's changed.
Let's console.log that payload for now, and then we call subscribe. This gives us back a channel which we want to unsubscribe from when our component is no longer mounted. We can return an arrow function and call supabase.removeChannel, and pass it that channel variable.
Now, if we go over to the browser, open up the console and add another tweet, we'll see our payload in the console. If we open this up, we can see more information about what's happened. What our eventType is, this one was an INSERT. We can also see the new record that's been inserted. This gives us the information from the tweets table.
If we wanted to use it to update our UI, we'd also need the author's name, their username, and the number of likes for this particular tweet. Rather than trying to stitch all of that together in our callback function, we can just call router.refresh, and we can get an instance of the router above by calling the useRouter hook, which comes in from next/navigation.
Now, because the callback function has been triggered, we know one of the rows in the tweets table has changed. By calling router.refresh, it's going to reload our server component, which is going to fetch fresh data from supabase, then transform it into the shape that we want in our application and pass it down to our Tweets component, which is then adding all of our optimistic UI logic.
The last thing we need to do is fix up our dependency array on our useEffect because we now have a dependency on the router as well as supabase. Let's add those to the dependency array.
Now, back over in the browser. If we have our Jon user on the left, and our Cooler Jon user on the right, if either of them put up a new tweet, both UIs are updated immediately without needing the user to refresh.