In this series, I am creating a Twitter clone with React and Supabase. If you follow along, by the end of the series, you will have deployed a fully functioning app that lets users:
- tweet out what they are thinking,
- upload avatars and change their profile,
- be notified when there are new tweets, and;
- be notified when someone has liked their tweet.
In Part 1, we have:
- Created a new Supabase project and quickly learned where thing were in the the dashboard.
- Used Supabase's
User Management Starter
template to quickly make aprofiles
table, enabled Row Level Security in the table, and set up storage access policies for users to upload their avatars. - Started a new typescript React project using
create-react-app
and installedmaterial-ui
as the UI library.
We won't be using the profiles
table yet. This is because to set up a profile, you need the users to have signed up in the first place! Fortunately for us, Supabase comes with authentication built in. In your dashboard, you can see your current users by going to Authentication tab.
![[P2 - where authentication is.png]]
If you remember, we've referenced this table when we were creating profiles
table. We've set up profiles
so that the primary key, id
is also a foreign key for auth.users
table.
https://gist.github.com/2d54ed0881172b1fbbfd1983166bb3bf
In this part, we will be setting up a basic user authentication flow, so that the app can let users sign up, sign in and sign out. Then, we will build our profiles.
To achieve this goal, we will set up:
- client-side routes with
react-router-dom
, - client-side validation with
react-hook-form
, and finally; - call Supabase's authentication API with the handy official client
@supabase/supabase-js
.
Let's get started!
react-router-dom is the go to library for setting up client-side routing in react. Add it to the project with: https://gist.github.com/85d6bedd7d80839c532e1dc893194167
Go to App.tsx
and replace it with below
https://gist.github.com/2e682c2ad146f38d84d48eb39d928926
The above code sets out 4 routes:
/signup
, where users can sign up,/signin
, where users can sign in,/signout
, where - you guess it! - users can sign out, and;- finally
/
, which is the root of the website.
As you can see, those routes don't do anything but display their names. We'll implement them now.
A sign up form requires just three fields:
- email,
- password,
- confirm password (to make sure the passwords match)
Create a folder called pages
inside the src
folder. Create a new file called signupPage.tsx
, then paste in the below snippet.
https://gist.github.com/673a3ff1d71f72540842ea52ae0d07fb
Go to App.tsx
and add this component to the /signup
route
https://gist.github.com/8fc8c57779fb4e5363f7ad2cb25ed350
Now when you yarn start
and go to /signup
, you should see this. If you click the 'sign in' button, you'll just get an alert saying "signed up"!
![[p2-basic-sign-up-without-validation.png]]
Beautiful. But the form still has a long way to go.
- It doesn't do any client-side validation, so our users can whatever they want, and;
- Obviously, it doesn't actually call our Supabase back-end.
Let's implement some client-side validation first! We'll be using react-hook-form
library to help us out.
Before we let users send the form off to our Supabase back-end, we want to check that:
- the email the user gave us is a valid email,
- the passwords to have at least 8 characters, and;
- the passwords in
password
field andconfirm password
fields should match.
We'll be implementing this logic with the help of react-hook-form
, so let's install react-hook-form.
https://gist.github.com/2bbb8a737c7f2290e5dbbabb4f8509ed
Copy in the below snippet into signupPage.tsx
to replace the existing code.
https://gist.github.com/e8ab6710065a0b5df008c25035baf921
Run the project again. Try to submit without having matching passwords. The 'confirm password' should turn red with an Error message and the alert()
won't get called.
![[client-side-validation.png]]
Lets go through the new code. You probably noticed that I've annotated the code with numbers, so you can easily find the relevant part.
We use the useForm()
hook from react-hook-form
to get functions we will be using to wire up our form to the library.
https://gist.github.com/724429797303400e4ff1814a2d229247
register
is used to registerinput
elements to track them, usinginput
elements'name
props. I'll be explaining this in more detail at point (3).handleSubmit
is used to wrap ouronSubmit
callback - it will stop the wrapped function (in our caseonSubmit
callback) from being called if there are any validation errors.watch
is used to track a registered element. In the snippet above, we gave the name"password"
to our password field and usewatch("password")
to watch the value of the field. See point (2) below for more explanation.errors
is an object that gets populated when there are - you've guess it - errors as a resut of the validation. If a field named"password"
is invalid, then the error object will a property callederror.password
withmessage
and other useful properties. Read the API doc here for more details.
As I've explained above, we use watch("[inputName]")
to have access to the current value of the watched input. We will use this later to make sure the passwords are matching before we let users submit their forms.
https://gist.github.com/a48331e683bf00dc573708593237e08c
https://gist.github.com/ed1c22602af86a6d379f47e6ce54eaf9
You must give the input a name and register the component by passing in register()
to the ref
prop. Normally, if you had a plain input, you'd pass in register()
to the ref
prop like below:
https://gist.github.com/e7219cc1444bc66304af39eeedd3093e
However, we are using a UI library and <TextField />
component doesn't have ref
property. Luckily in our case, Material UI exposes inputRef
which is passed down to the <input />
element inside <TextField />
component. If your UI library of choice does not expose input
's ref
like Material UI does, there are other ways to integrate in the official documentation.
https://gist.github.com/18cda92244ee2bfe8b9bb0ce00d8777b
You can pass an object with validation rules to the register()
function. In the snippet above:
required: "You must provide an email"
says that this field is required, and the error message when they don't fill it out (later given aserrors.email.message
)pattern: emailRegex
says that the value of the field must match the emailRegex (that is defined at the top of the file).
Of course, there are other validation rules and you can find them in [the official documentation] (https://react-hook-form.com/api/useform/register/).
Instead of preset validation rules, we can also pass in a callback. I've used a callback to check that the passwords are matching, when I registered the 'confirm password' input field. https://gist.github.com/c1fadecd97424ac6217bb5b0e1bc6f16
Once we've set up validation and wired up our components to react-hook-form
, we need to tell our users what they did wrong when they've given us invalid inputs. This is done with errors
object we get from useForm()
.
https://gist.github.com/fe822491fc164f1b7f48f195734bfa88
errors
object will be an empty object when there are no errors, but when react-hook-form
detects invalid inputs, it will add an object to the errors
with the offending input's name
. So for example, if an invalid input was given to the <input name="email" ref={register({ required: true })}/>
then errors
object will have look like below.
https://gist.github.com/c44cca248dbb04a41d302783aee4d8e6
We use this behaviour to show errors via Material UI. When you pass a truthy value to Material-UI's TextField
component's error
prop, it will turn red and display what is set as helperText
prop.
When there are no errors, the errors object will be null
, so I use optional chaining to easily check against undefined
.
Now that we have some client-side validation, let's wire up our sign up page to Supabase!
To connect our app to Supabase, we need to:
- install the Supabase JavaScript client.
- initialize the client by giving it our Supabase app's URL and the API key.
Let's first install the client. https://gist.github.com/1200474caf6d6015d03cb1fc53312541
As you can see, we need to get the URL to your app and the key. You can get both by going to your project in Supabase, then Settings (cog icon), into API.
![[url-api-keys.png]]
Copy the URL and replace the value of supabaseUrl
with it. Do the same with the API key. It's a public key, so its fine to hardcode it in (just make sure you've turned on row level security on!). Personally, I've used dotenv
that comes with react-scripts
instead of hardcoding it in the codebase, but the end result would be the same anyway (it would be baked into the source code).
Anyway, go to your signUpPage.tsx
file, import the supabaseClient
and change the onSubmit
function to below:
https://gist.github.com/0fc82683fa52919aa1218c49ed353cb0
Now, restart your app, go to /signup
route, and try to sign up. You should get an alert saying that you've signed up successfully! Go to the Authentication section in the Supabase. You should see your email in the list of users.
![[user-in-baby.png]]
Go to your email inbox, you should see the confirmation email there too!
![[confirm-signup.png]]
We've got the basic sign up flow working. Let's go and set up sign in and sign out flows. Create two files, signInPage.tsx
and signOutPage.tsx
in the pages
folder. Paste the below code into signInPage.tsx
.
https://gist.github.com/705fb5f0f38797e46226e74bd6700ce9
And paste the below code to signOutPage.tsx
.
https://gist.github.com/2369a6c417ae96ba8b03492f53abf07e
Then add these page components to the routes in App.tsx
https://gist.github.com/38b04cb9728479a7e788ab1471b3c9d9
Now, you should be able to navigate to /signin
route to enter your credentials and sign in and when you navigate to /signout
route, you will be signed out.
![[signin-screen.png]]
You might notice that click the sign in button, nothing happens! Well, you are probably getting logged in, but we aren't doing anything after that. I also said probably because we aren't checking for errors.
Just so we know that the log in worked, lets redirect the user to home /
when they successfully log in. In the home page, we will show the user's email on the home page if they are logged in.
First make the following changes to the SignInPage.tsx
https://gist.github.com/9b7d5bbefa2e0e84b69c0ee7a51a1e66
Then add these lines to the top of App.tsx
.
https://gist.github.com/bca47a8350ecaaafc6bfc5d1f4f350fb
supabaseClient.auth.session()
gets the current session (from the local storage) if there is one. You can see this by checking the local storage option in your console.
![[supabase-get-session.png]]
This is arguably
Now when you sign in, you will be redirected to home page and you will be greeted with the message we've just added.
![[signin-redirect.gif]]
Note: I'm pretty sure securing tokens in the local storage isn't the most secure practice, but we won't go into the details in this blog post (and I'll be doing more research too).
We have very basic sign up, sign in and sign out flows, but it is still missing a lot of features and polish. Don't worry, we'll get to them in the next part. In Part 3, we will be making:
- things slightly more prettier,
- the nav bar with links, refreshed when user logs in or out, and;
- secured routes where a user must be logged into access (or redirected to
/signup
route.
By implementing above things, we will be learning:
- how to use and inject React Context into components, and
- how to use Higher Order Components to keep things DRY when we secure routes.
Thank you for reading through this long tutorial. Let me know if you have any questions via comments or via Twitter @James_HJ_Kim