Using React Context, we can create global variables that are available throughout our Next.js application. In this video, we create a useUser
hook that exposes the state of our Supabase user to our tree of React components.
To get the state of our Supabase user when our application loads, we can use supabase.auth.user()
. When the state of our user changes - e.g. our user logs in - Supabase will call the supabase.auth.onAuthStateChange
callback. We can use this to update the state of our user object whenever the user signs in or out.
In order to consume our user context, we need to wrap our root component in our Context Provider. This can be done in the pages/_app.js
file. Now any component in our tree of decedents can call useUser
to get the state of our global singleton user.
Lastly, our user object only contains values from the auth.users
table. In order to enrich our user with data from the profile
table, we need to add a request to our Supabase db within our user provider.
Instructor: [0:00] Currently, when we log in, we get redirected to our Landing page, but our user is actually null. If we refresh, we'll see our user object, but it only contains data from the auth.users table.
[0:10] We also want to know throughout our application, whether we have a strat customer and whether they're actually subscribed. Let's keep a track of our user in a context object, which can be shared across our entire application. Let's import createContext from React.
[0:24] Let's create a new context object. Now we can create our Provider component, which is going to wrap around a collection of children. Let's make that the default export. Next, we want to bring in useState and useEffect and create a new variable for our user.
[0:39] This will be initialized to our Supabase.auth.user, which means we'll need to import our Supabase client. Now in our useEffect, we want to listen to any changes on our auth object, which we can do by saying Supabase.auth.OnAuthStateChange.
[0:57] We're going to pass this a function, where we're just going to set our user to Supabase.auth.user again.
[1:04] Whenever our user refreshes the application, they'll get whichever user is currently set to Supabase.auth.user. Then anytime our user logs in, this onAuthStateChange function will be called and will set our user to be whatever the new state of our Supabase.auth.user is.
[1:20] We then need to declare what we actually want to expose from our Provider. In this case, our user. We pass that into our context provider as the prop value.
[1:30] We can then bring in useContext from React and export out a useUser hook by setting it to a function that calls useContext and passes in our context object. We now open up _app.js, and we can wrap this component in our new provider.
[1:49] Back in our Landing page, we want to import useUser from ../context/user. Call that function to get our user in our component and then console log out our user. When we open up our console, clear what's there, and simulate logging out and back in.
[2:07] We'll see that our user is initially set to null. Then once that login process has happened from GitHub, we have our user. However, when we log out, we would expect our user to be set to null, but it's set to our user object.
[2:21] That's because back over in our userProvider, this onAuthStateChange function is only being executed when we log in and not when we log out. We can handle this manually by moving our Login and Logout functions up to our userProvider. We can also move this routing logic up. Then after our user has successfully signed out, we can set our user to null.
[2:52] We can now expose our Login and Logout functions from our Provider and import them in our Login and Logout pages. Since our useEffect is just declaring a function that's calling another function, we can just pass it a reference to the Login function instead.
[3:12] We can do the same for Logout. We can clear our console and log out and back in. We're seeing that our user is successfully set to null when we log out. It's initially null when we're in the login process. Then once it's finished, we have our user object.
[3:36] If we log out again, our user is set back to null. We have our authentication working correctly with our useUser hook. When we do login, our user contains only those values from the auth.users table. We want to enrich our user with the columns from our Profile table, so we can tell anywhere in our application whether our user is subscribed or not.
[3:58] Let's go back to our userProvider and inside our useEffect, we want to create a new function called getUserProfile. Here we want to get the current session user from Supabase.auth.user.
[4:11] We then want to check if we actually have a session user. Then we want to make a request for their associated profile. We can say, Supabase.from the profile table, select all the columns where the ID column is equal to our session user.ID.
[4:24] We just want to get back a single row. We then want to merge the data from our profile with our session user. We can say, set user and pass it a new object where we spread in our session user and our profile.
[4:36] We then want to call our getUserProfile function when our component mounts and also whenever the auth state changes. If we clear our console and log out, we'll see our user object is set to null.
[4:49] If we log in again, we will see our user object complete with our fields from auth.users, as well as our profile columns like interval, isSubscribed, and Stripe customer.
Hey! Very good question! This is because the first render of the Next.js application happens on the server (SSR), and our useUser hook uses a useState variable - only available on the client.
One way we can get around this is by using Next.js' getServerSideProps
function, pulling the user out of the cookie and returning it in the prop
object making it available to our component. This will be available during the first render (SSR) and for the regular client-side renders 👍
What is the best approach for setting this context and avoiding conflict alongside the Supabase auth helpers. For example, if app is already wrapped in SessionContextProvider.
Hey Jake, I don't have a specific answer for you but the Supabase GitHub discussions is active and someone might be able to help you there: https://github.com/supabase/supabase/discussions
No worries, I just went ahead and used the Supabase auth helpers for session context and a custom context for user account/profile. Thanks for everything, great tutorial!
This may need to be updated to make use of the supbase auth helpers. They now provide a useUser
hook [1]
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
[1] https://supabase.com/docs/guides/auth/auth-helpers/nextjs#client-side-data-fetching-with-rls
Hey, I'm trying to access the user object from useUser in a component, but if I try to render {user.username} I get a "Cannot read username property of null" error.
The user appears in my console.log, but I'm suspecting it first tries to access the property on the object, before the user object is fetched from Supabase.
Any idea how I can remedy that? I was thinking I can provide some default values for the user object before I get it from Supabase, but not sure that's best practice. Thanks!