The OAuth flow is asynchronous. This means we get redirected to the landing page before GitHub and Supabase have decided that you can be trusted! π
Since our Loader functions get called when we first load the page they make a request to Supabase before receiving a valid access token. This causes RLS to deny the request to select data.
Once our session has been correctly set, Supabase is happy but we aren't telling Remix to fetch this data again. In this lesson, we look at combining the onAuthStateChange
hook from Supabase with the useRevalidator
hook in Remix to recall any active loaders
when the state of our user changes, keeping data in sync with mutations.
Therefore, any time the user's session is updated - the auth flow has completed for either signing in or out - Remix will automatically call all loader functions that are active on the current route (this could be many with nested routing), fetching fresh data from Supabase with a valid access token.
onAuthStateChange listener
useEffect(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== serverAccessToken) {
revalidator.revalidate()
}
});
return () => {
subscription.unsubscribe();
};
}, [supabase, serverAccessToken, revalidator]);
Instructor: [0:00] Our application is only showing messages to users that are signed in. However, when we signed out, you'll see that we're still seeing these messages and we need to refresh before seeing that new state. Then again, if we login, the same thing happens and that's because the logging in process redirects us to this page before GitHub has completed its OAuth process. [0:19] We're loading our page before our Auth state has actually changed. This will cause Remix to call our loader, which attempts to go to Supabase and get all the messages that this user is allowed to see. But Supabase says, "Well, this person isn't signed in," so you get an empty array.
[0:34] Then that OAuth flow from GitHub completes and our user is now signed in, but nothing's telling Remix to go and call that loader again to get that authenticated data from Supabase. We need to detect when our Supabase Auth state changes and tell Remix we need to go and call all of those loaders again and get fresh data.
[0:53] Thankfully, Supabase gives us a hook to do exactly that.
[0:56] Back up in our root.tsx file, let's go down to our component and we can get rid of these console logs now. Since we're no longer using this useEffect, we can repurpose it to call supabase.auth.onAuthStateChange. Then this takes a function which will be passed an event and a session anytime the user's auth state changes.
[1:18] Then we want to check if our session has an access token and its value is different to our serverAccessToken, which is a variable we can declare above. We can set this to be equal to our server's sessions access token, which is what we got back from our loader here.
[1:38] If our new access token that we've received client-side doesn't match our server's access token, then it means that the data that our user is seeing is now out of sync. And so, we need to call our loaders again.
[1:50] We can do this using a revalidator. Let's create one of those above by calling the use revalidator hook, which comes in from @remix-run/react. Then back down in our callback function, we can say revalidator.revalidate. This is going to automatically call any active loaders on the page.
[2:11] Now, since onAuthStateChange creates a new subscription, we need to make sure we clean this up in our useEffect. We get back a data object and this data object has a subscription on it. And so to clean this up, we can return an arrow function from useEffect and in here call subscription.unsubscribe.
[2:33] Lastly, our useEffect now has an external dependency on Supabase and serverAccessToken and also our revalidator. Let's add those to our dependency array.
[2:42] Now, when we head back to the browser and refresh, we'll see we've still got that empty state but now when we login, we'll see the messages we're meant to see as a login user. Again, when we log out, we'll see that empty state without needing to refresh.