Supabase Realtime allow us to subscribe to change events in the database - insert
, update
and delete
- and update the UI dynamically. In this lesson, we enable replication
on the messages
table to tell Supabase we want to know about changes to this table.
Additionally, we use the Supabase client to set up a subscription for insert
events on the messages
table. This will receive websocket updates from Supabase, which we can handle in a callback function, and merge our server state with realtime updates, to allow our application to dynamically update as new messages are sent.
Subscribe to realtime updates
useEffect(() => {
const channel = supabase
.channel("*")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "messages" },
(payload) => {
const newMessage = payload.new as Message;
if (!messages.find((message) => message.id === newMessage.id)) {
setMessages([...messages, newMessage]);
}
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [supabase, messages, setMessages]);
Enable realtime events on messages table
alter table public.messages
replica identity full;
SQL code snippets can be run against your Supabase database by heading over to your project's SQL Editor, pasting them into a new query, and clicking
RUN
.
Instructor: [0:00] Currently users can send messages, but the UI is only updated for them. If we have another user who sends a message, they'll only see the first user's message once they either refresh or send a message themselves. Again, our first user would need to refresh if they wanted to see that second user's message. [0:16] Let's enable real time to keep these two users in sync. Let's start by going to our Supabase dashboard and then clicking on database, and then replication. If we then click where it says zero tables here, we'll see that we haven't enabled replication for our messages table. Let's do that now.
[0:31] Replication is what Supabase uses for sending any database changes over WebSockets. Let's subscribe to those changes in our Remix application. I'm going to create a new component called realtime-messages.tsx. We can then declare a real-time messages component which takes in a server messages prop, which we can see here is just an array of messages.
[0:53] Without generated types from Supabase, we can dig into a particular table by going Database ["public"] ["Tables"]["messages"]["Row"]. This gives us the declaration for an individual Message. We're then just returning the JSX for our pre tag and pretty printing out our serverMessages.
[1:09] Over in our routes/index.tsx file, instead of rendering out the messages that we got back from the server directly, we can call our RealtimeMessages component which comes in from components/realtime-messages, which takes in a prop for serverMessages, which we can set to the messages that we got back from our loader.
[1:31] If we save this file, go back to the browser, and refresh, we should see that nothing has changed. Back over in our RealtimeMessages component, we're going to be taking our server state and merging it with real-time events. Therefore, we're going to need a dynamic variable that can change over time.
[1:47] Let's create a new messages variable and setMessages function by calling useState, which comes in from React. Then, we're going to give it to the default value of our serverMessages. To subscribe to updates from Supabase, we're going to call useEffect. That comes in from React. We want this to run when our component mounts.
[2:07] In here, we're going to use supabase, which means we need to bring this in from our context. Again, we can type our context as SupabaseOutletContext which comes in from our route. We also need to bring in useOutletContext from @remix-run/react. We can now, say supabase.channel.
[2:28] We want to listen to all channels and say .on to say the changes that we want to listen to our 'postgress_changes'. We then need to specify which events we care about so in this case, just 'INSERT'. We can also specify that the schema is going to be public and that the table that we care about is messages. This will then take a callback function to run any time a new row is inserted into the messages table.
[2:52] This automatically gets past a payload, which contains information about what has changed. And so, for now, let's just console log whatever is in that payload, and now we just need to call .subscribe. This will then give us back a channel.
[3:06] And so to perform our cleanup, we can return an arrow function, which calls supabase.remove channel and pass it our channel. Now that this useEffect has an external dependency for Supabase, we want to add it to the dependency array here. Now, if we go back to the browser and refresh both of our users, let's also open up the console.
[3:28] When we scroll down and type a new message, we'll see that both users receive that payload which has a new object with the contents of our new row. Over in our callback function, rather than just console logging out our payload, we can create a variable for our new message, which is going to be set to payload.new.
[3:50] We can declare this is going to be of type message. Because the sender's UI is updated when the action completes, we want to make sure that our messages array doesn't already contain this new message. If our message is not currently in the message as array, which we can check by calling find which will run over each message.
[4:06] We can see whether that messages ID field is equal to our new messages ID field. If we can't find that message then we want to call set messages and construct a new array by spreading in our current messages and then appending our new message. Now, our useEffect has a dependency on messages and set messages.
[4:27] Let's add those to the dependency array here. We want to render out our dynamic messages rather than just the messages from the server and then to update our component if our server messages change, which will happen if the user signs in or out as Remix will recall all active loaders.
[4:43] And so, therefore, we need another useEffect here, which will run any time our server messages change and update our dynamic messages variable, and then head back over to our Supabase instance and remove some of these messages to clean up our UI.
[4:57] Now, when we go back to the browser and refresh both of our users, our first user can send the message Hello and we see that the UI is updated for both users. Our second user can then respond with the message Hi, showing that the UI is updated dynamically any time the database changes keeping the UI for all of our users in sync.