A Step-By-Step Guide to Creating a Twitter Clone With React and Supabase — Part 2

James Kim
Geek Culture
Published in
9 min readApr 10, 2021

--

In this series, I am creating a Twitter clone with React and Supabase. By the end of the series, we will have deployed a fully functioning app that lets users:

  1. tweet out what they are thinking,
  2. upload avatars and change their profile,
  3. be notified when there are new tweets, and;
  4. be notified when someone has liked their tweet.

In Part 1, we have:

  • created a new Supabase project and went over where things were in the dashboard,
  • used Supabase’s User Management Starter template to quickly make a profiles table, enabled Row Level Security in the table and set up storage access policies for users to upload their avatars, and;
  • started a new typescript React project using create-react-app and installed material-ui as the UI library.

We won’t be using the profiles table yet. This is because you first need the users to have signed up, for them to set up a profile! Fortunately for us, Supabase comes with authentication. In your dashboard, you can see your current users by going to the Authentication tab.

I have my test users, but you shouldn’t see any users 😅

You might remember that we’ve referenced this table when we were creating the profiles table in Part 1. When we’ve created the profiles table, we’ve set the primary key, id , as a foreign key, referencing the auth.users table. I’ve included the snippet below:

Remember this SQL?

Thus in Part 2, we will be setting up a basic user authentication flow, so that the app can let users sign up, sign in and sign out. To achieve this goal, we will set up:

Let’s get started! run

Setting up client-side routes with react-router

react-router-dom is the go-to library for setting up client-side routing in react. Add it to the project with:

yarn add react-router-dom 

And add the typings with

yarn add --dev @types/react-router-dom

Go to App.tsx in our react project and replace it with below code.

The above code sets out 4 routes:

  • /signupwhere users can sign up,
  • /signinwhere users can sign in,
  • /signoutwhere - you guessed 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.

Implementing a basic sign up page

A basic signup form requires just three fields:

  1. email,
  2. password, and;
  3. 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.

Go to App.tsx and add this component to the /signup route

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"!

Beautiful 😍. But the form still has a long way to go.

  1. It doesn’t do any client-side validation, so our users can submit whatever they want, and;
  2. Obviously, it doesn’t actually call our Supabase back-end.

Let’s use the react-hook-form library to implement some client-side validation.

Client-side validation with react-hook-form

Before we let users send the form off to our Supabase back-end, we want to check that:

  1. the email the user gave us is a valid email,
  2. the passwords to have at least 8 characters, and;
  3. the passwords in password field and confirm password fields should match.

We’ll be implementing this logic with the help of react-hook-form, so let's install react-hook-form.

yarn add react-hook-form

Copy in the below snippet into signupPage.tsx to replace the existing code.

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.

You might have noticed that I’ve annotated the code with numbers, lets go through the numbered part, one by one.

(1) add useForm() and take what we need

We use the useForm() hook from react-hook-form to get functions we will be using to wire up our form to the library.

  • register is used to register input elements to track them, using inputelements' name props. I'll be explaining this in more detail at point (3).
  • handleSubmit is used to wrap our onSubmit callback - it will stop the wrapped function (in our case onSubmit 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 use watch("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 guessed it - errors as a result of the validation. If a field named "password" is invalid, then the error object will a property called error.password with message and other useful properties. Read the API doc here for more details.

(2) use watch() to track the value in the password field

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.

(3) We register the input element, passing register() to ref property of the input.

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:

However, the <TextField /> component doesn't have ref property. Luckily for us, Material UI exposes inputRef which is passes down the prop to the ref of the <input /> element. If your UI library of choice does not expose input's ref as Material UI does, there are also other ways in the official documentation.

(4) declare what validations the field needs.

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 as errors.email.message)
  • pattern: emailRegex says that the value of the field must match the email regex (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.

Instead of preset validation rules, we can also pass in a callback to use custom validation logic. In the code, I’ve used a callback to check that the passwords are matching when I registered the ‘confirm password’ input field.

(5) Show validation errors to users via errors object

We’ve set up validation and wired up our components to react-hook-form, but we aren’t telling our users what they did wrong when they've given us invalid inputs. We can do this by utilising the errors object we got from useForm().

errors object will be an empty object when there aren’t any 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 theerrors object will have an email property with a FieldError object.

usually, we just care about the ‘message’ field

In the code, we’ve used this 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 our error message, which is passed into thehelperText prop.

When there are no errors, the errors object will be null, so I use optional chaining to check against undefined.

Now that we have some client-side validation, let’s wire up our sign-up page to Supabase!

Connecting sign-ups page to Supabase

To connect our app to Supabase, we need to:

  1. install the Supabase JavaScript client.
  2. initialize the client by giving it our Supabase app’s URL and the API key.

Let’s first install the client.

yarn add @supabase/supabase-js

Add a folder called /api in the src folder, then add a file called supabaseClient.ts. In the file, copy in the following snippet.

import { createClient } from '@supabase/supabase-js'const supabaseUrl = 'https://URL_TO_YOUR_APP.supabase.co'
const supabaseKey = 'Your supabase key'
export const supabaseClient = createClient(supabaseUrl, supabaseKey)

As you can see, we need to get the URL to your app and the API key. You can get both by going to your project in Supabase, then Settings (cog icon), into API.

Copy the URL and replace the value of supabaseUrl with it. Do the same with the API key. It's a public key, so it's fine to hardcode it in (just make sure you've turned on Row Level Security!).

Go to your signUpPage.tsx file, import the supabaseClient and change the onSubmit function to below:

Now, restart your app, go to the /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.

Go to your email inbox, you should see the confirmation email there too!

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.

And paste the below code to signOutPage.tsx .

Then add the new pages to the routes in App.tsx .

At the moment clicking the sign-in button does nothing! Well, you are probably getting logged in, but we aren’t doing anything after that.

Just so we know that the sign-in worked, let's redirect the user to home / when they successfully log in. And on 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

Then add these lines toApp.tsx.

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.

Note: I don’t think securing tokens (especially refresh tokens) in the local storage is the most secure practice, but we won’t go into the details in this blog post. I may do more research and write about it later 🔬

Now when you sign in, you will be redirected to the home page and you will be greeted with the message we’ve just added.

We have a very basic authentication flow, 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:

  • make things slightly prettier✨,
  • add a navbar with links, refreshed when the user logs in or out, and;
  • create some secured routes where a user must be signed in to access (or redirected to /signup route.

By implementing the 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 🙏 Follow me on Twitter(@James_HJ_Kim) so you don’t miss Part 3! If you have any questions, let me know by commenting on this post or by Twitter. See you in Part 3!

--

--

James Kim
Geek Culture

Backend Engineer working at Xero. Also passionate about UX, writing and illustration.