Skip to content

Instantly share code, notes, and snippets.

@horizon0708
Created April 8, 2021 09:15
Show Gist options
  • Save horizon0708/4b6b4cf631465dd4a999ccf33175d6db to your computer and use it in GitHub Desktop.
Save horizon0708/4b6b4cf631465dd4a999ccf33175d6db to your computer and use it in GitHub Desktop.

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

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:

  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 quickly learned where thing were in the 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.
  • 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 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:

Let's get started!

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: 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.

Implementing a basic sign up page

A sign up form requires just three fields:

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

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.

  1. It doesn't do any client-side validation, so our users can whatever they want, and;
  2. 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.

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.

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.

(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. https://gist.github.com/724429797303400e4ff1814a2d229247

  • 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 guess it - errors as a resut 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 pw 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. https://gist.github.com/a48331e683bf00dc573708593237e08c

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

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.

(4) declare what validations the field needs.

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 as errors.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

(5) Show validation errors to users via errors object

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!

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. 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment