Welcome back! This is the second part of a three-part tutorial where we go through and build a PWA from scratch that utilizes a powerful architecture pattern called the JAMStack. You can find the blog post covering part 1 here. I’d suggest reading through part 1 first as we’ll be jumping straight into from where we left off.

You can find the repository for part 1 here, if you’d prefer to start fresh from where we left off. The completed code for part can be found here.

Setting up Firebase

Let’s get Firestore and authentication setup. As mentioned in the last post, firebase has a generous free tier, so do follow along as this step will be required to get the app working. Head over to console.firebase.google.com then add and create a new project called “Polling-app”. In the sidebar, under Develop click Database and select Cloud Firestore Beta.

At the time of writing this blog Cloud Firestore is still in Beta.

Select “Start in test mode”. Starting in test mode sets all read and write authorization rules for all users to true; authenticated or not. This is convenient for development, however, when it comes time to deploy your app. don’t forget to go back and update these rules. Hit “Enable”. Now go to Authentication in the sidebar and click Set up sign-in method. Enable both Google and Anonymous and hit save for both. We’ve enabled Anonymous authentication because we want to keep track of a non-authenticated user’s poll option selections so that, amongst other reasons, upon revisiting a poll, their selection will be saved. Keep in mind this by no means is a fool-proof way of making sure there won’t be any duplicate votes.

Adding Authentication

With authentication set up in the firebase console, let’s set up the accompanying UI and functionality.

More directory structure convention

Create two new directories inside ./src/ called services and containers. The services directory encapsulate modules that reach out to third party APIs. Inside these modules is code that pertains specifically to that service. In the case of firebase and our application: initialization code and a very thin abstraction layer on top of some of the firebase methods for convenience.

I use the containers directory for React components that encapsulate side-effects, functionality, and state. Container components are usually where we’d be making use of our services. I follow this pattern so that inevitably when bugs do arise, I’ve got a solid basis for trouble shooting. If you recall from part 1, I mentioned the components directory should only include stateless functional components. By following this pattern, you can be confident with a high degree of certainty that any bugs, outside of incorrect syntax, will not originate from your stateless functional components.

I sometimes treat components in the ./src/pages/ directory as container components so long as the functionality and state they encapsulate is specific to that page. However, it wouldn’t hurt to keep them as stateless functional components and abstract functionality and state into a container.

Initializing firebase

Create a firebase.js file in the services directory and install firebase.

npm install -D firebase

To get your initialization keys head back over to the firebase console and next to Project Overview click the settings icon then Project Settings. Under Your apps click Add Firebase to your web app. These keys will exist in your client-side code; this does not pose any security risks so long as you’ve set the aforementioned authorization rules. Copy the config object’s properties over to the config object defined in ./src/services/firebase.js.

The Auth Container

This component will contain all of our authentication state, methods, and a listener for the user’s authentication state changes. We’ll make use of render props to share state and functionality since HOCs are like so 2017. Create a new container called Auth.js.

Notice that we’ve defined contextTypes on the component. We’ll be passing a reference to our firebase singleton exported from our services directory via context. Let’s define that provider in the containers directory and call it FirebaseProvider.js.

Keep in mind as of writing this, the React context API has changed. This isn’t a huge concern as we’re using Gatsby 1.x in this tutorial which is locked in to React 15.x (look into gatsby-plugin-react-next to use the latest and greatest features from React). By extracting the code specific to the context API into their own components we‘ve made our upgrade path to React 16.x a bit more painless since the code is isolated in a handful of components.

Gatsby Browser APIs

We want to wrap our app with the firebase provider high up in the component tree so that if any child component needs a reference to the firebase object, they can get at it by defining contextTypes.

A good place to do this would be in one of the browser APIs gatsby.js exposes. Specifically replaceRouterComponent: an exported method defined in ./gatsby-browser.js that lets you override the default routing behavior. We wont be using it for anything related to routing; but instead as a place to wrap the application with our provider. If not already created by the gatsby cli, create a file called gatsby-browser.js at the root of your project.

Sign-in/Sign-out buttons

Let’s create a few UI components for our sign-in/sign-out buttons. Starting out with a Google icon component defined in ./src/components/icons/Google.js:

And a SignIn component in ./src/components/SignIn/index.js:

We’ll use the SignIn component in ./src/components/Header/index.js. Let’s update that file:

To get everything working, the Header component expects some props from the Auth container component. In ./src/layouts/index.js, let’s use our Auth component so that we can pass its state and methods as props to the components that need it.

As you can see we are spreading the auth object that contains our authentication state and methods to the Header component as well as to the children function. By passing those object properties to children(), they can be accessed by any component defined in our pages directory. This is how we’ll share code between the layouts and pages components with Gatsby.js.

Authentication is done! You should now be able to sign-in and out using a google account.

Creating a poll

With authentication out of the way let’s finish up the new poll page.

We’ve changed several things to this page:

  • added an input for the title of the poll the user is creating.
  • we’ve hooked up the Create button to the method handleCreate.
  • added loading state so that we can prevent the user from creating the poll multiple times or adding items to the poll after they’ve already created the poll.

Since we’ve wrapped our application with the FirebaseProvider, like the Auth container, we can give the component the ability to access the firebase object by defining contextTypes. In the handleCreate method we access it by referencing this.context.firebase. When handleCreate is invoked, it sets the loading state to true, gets the reference to the polls collection via the getter we’ve defined in services/firebase.js, creates a document in the polls firestore collection with an id generated by short-id, sets the relevant properties on that document, then makes the http request to save that document in the database.

When using firestore, no http requests are made until we invoke .then() on the returned Promise from the .set() method. The same is true for .get() invocations.

From inside the .then(), we know that the document has been saved. So we reset the component’s state, then finally, we use the reference to the history object that’s accessible from props to automatically navigate the user to their newly created poll.

I’m neglecting to handle error cases for the sake of time. This is absolutely something you shouldn’t do in a production ready application.

To finish up this page, let’s update our Button component in theme.js file located in the styledComponents directory to make use of the disabled props we’re now passing it:

The poll page

By default Gatsby.js does not support dynamic routes with route parameters: the type of route we’re trying to use with our poll page. e.g mysite.com/poll/:pollId. In order to accomplish this we need to add a plugin: gatsby-plugin-create-client-paths. What this will do is have Gatsby skip out on generating the markup for a specified route, effectively making that route’s page component behave like a single page application. In our case, in the plugins config, we’ll set the route /poll/*, to be handled by the client. Install the plugin and update ./gatsby-config:

npm install gatsby-plugin-create-client-paths

Create a new a stateless functional component in components/Poll/index.js:

Then a container component for it:

The Poll container component handles the following:

  • checks to see if the route-parameter is valid. If not, it will redirect the user to /404.
  • logs the user in as anonymous if they’re not already authenticated.
  • selecting and voting on an option.
  • checking if the user has already voted after being authenticated.
  • setting up and removing a listener on the results sub-collection of the poll so that the user can get real-time updates when other users vote.
  • any state related to voting.

Finally, let’s hook up the Poll container to the app in ./src/pages/poll/index.js:

Here we’re using react-router-dom to handle the client-side routes for this specific page specified in gatsby-config.js. If a user lands on the /poll/ route which doesn’t exist, they’ll be redirected to the homepage. Otherwise, the Poll container components handles the rest.

A production build problem

There’s a bit of an issue with these client-only routes and Netlify. Netlify is just a CDN so by default it has no idea about the existence of these client-only routes. If we were to deploy the application, the first thing the user would see when navigating to a created poll is the 404 page, then after the javascript is parsed and executed, the expected page would be seen. Fortunately, there’s a solution; we can specify a _redirects file so that Netlify redirects any /poll/:pollId parameter routes to right page. This can be accomplished by just adding a _redirects file at the root of the folder that’s being deployed. In the case of Gatsby.js, that’s ./public/. The problem with that is that the public directory is generated every time we run the build script. To automatically get a _redirects file into the right directory, we can add another directory called static at the root of our project. Gatsby.js looks for this directory at build time and places all of its contents at the root of the public directory. The files inside the static folder are not processed by Webpack like the rest of the assets in the project located in ./src/.

You can read more about the static directory here. Create a new directory called static at the root of your project and create a file called _redirects with the following content:

/poll/:param /poll 200

Deploying the application with Netlify

It would be convenient to be able to deploy the application without continuous deployment (which we’ll set up in a moment) so that we could get a shareable link to test the app in its current state on other devices. Let’s use Netlify’s CLI to accomplish this. Head over to netlify.com and create a free account. Then install the CLI:

npm install netlify-cli -g

Once installed, lets update our package.json. Add the following deploy script onto the scripts property:

"deploy": "npm run build && netlify deploy"

Run it. First, the build script is ran which will generate a public directory with our application code transformed for production and any contents in ./static/ moved into it, then netlify deploy is ran. The first time you run netlify deploy, you’ll be asked to authenticate. Once authenticated, you’ll be asked if you’d like to create a new site — hit Y for yes. Then you’ll be asked for the path to deploy — specify public.

We need to add all Netlify links as authorized domains in order to get OAuth working on those generated links. Head back over to the firebase console and back into the authentication pane. In the sign-method tab under Authorized domains hit add domain and enter netlify.com.

If you’re deploying the site on another domain, be sure to add it here.

You should now be able to test the application using the generated links.

Continuous deployment

One of the best features of Netlify is how incredibly easy it is to set up continuous deployment. Since there are no tests in this application currently, we’ll use a rather naive approach and deploy our application to production any time we push to master without any checks. Stage and commit your changes with git if you haven’t already, then create a repository on github.com and push your code to it. Head over to app.netlify.com and hit new site from git. Under Continuous Deployment, select Github. Once authenticated, select the correct repository. Notice that the build command and publish directory fields are already populated with gatsby build and public/ respectively. Netlify is clever enough to know that the repository is a Gatsby project and sets those fields to the correct values — yet another reason deploying with Netlify is a breeze. Hit Deploy site.

That’s it! Anytime you push to master, Netlify will automagically deploy and host the most recent build of your app.

Firestore security rules

Let’s add security rules since our application is now accessible by the public. Go to the firebase console, in the database pane under the rules tab paste in the following rules:

service cloud.firestore {
match /databases/{database}/documents {
match /polls/{document=**} {
allow read: if true;
allow create: if request.auth.uid != null;
allow update: if false;
allow delete: if false;
}
}
}

Here we’re applying rules to all documents in the polls collection and any sub-collections. We’re allowing anyone to read any data from these documents, only allowing authenticated users to create documents, and we’ve set the update and delete rules to false since our application currently doesn’t support these operations. You can read more about the firestore security rules here.

Generating a Lighthouse report

Lighthouse is a tool that runs several tests on a webpage to test how “progressive” your site is. That is it has several audits for performance, whether or not you’ve utilized key PWA technologies, accessibility, best practices, SEO, and more. After running Lighthouse on your site, a report with scores broken down by the aforementioned criteria will be generated with tips on how to improve those scores. Let’s see where our application currently stands. After running the test several times, I’ve picked out a good median result.

This concludes part 2 of this tutorial. We’ve got a bit to improve on with respect to performance and some key PWA technologies like service workers and a manifest file. Stick around for the next part where we’ll make our application a bit more progressive and in turn performant by utilizing service workers and several other key technologies.

If you have any issues or would like to see the completed code please visit our repository here.

Be sure to follow us for more blogs every Monday and Tuesday! We’ll have Part 3 out for you in no time. See ya!

John Korzhuk is a software developer and proud Unicorn. He specializes in full-stack javascript, React.js, and web development. Outside of work his interests include gaming, esports, and learning about anything that he finds interesting. You can find on twitter @johnkorzhuk.

Want to build magical experiences together? Contact us!

E-mail: adam-leonard@unicornagency.com

Twitter: @UnicornHQ

Website: http://www.unicornagency.com

Unicorn Agency

Written by

A Software/Product Development & Design Agency, building magic. Contact a human at social@unicornagency.com #software #development #miamitech #newyork

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade