Build Progressive Web Application (PWA) with React

Build Progressive Web Application (PWA) with React


In this article, we will create a React PWA that has reusable React components and styling using Create React App and styled-components. The application will use data fetched from an external REST API.

Pre-requisite

  • Basic understanding of JavaScript ES6
  • Basic understanding of HTML and CSS
  • Basic understanding of react
  • Have Nodejs installed on your machine

What is a progressive Web Application (PWA)

Progressive Web Application (PWA) is a web application that behaves like a mobile application.
It is usually faster and more reliable than regular web applications as it focuses on an offline/cache-first approach. This makes it possible for users to still open our application when they have no or a slow internet connection due to its focus on caching. Also, users can add our application to the home screen of their smartphone or tablet and open it like a native application.

Project setup

Let’s get started as we create a new project using the create-react-app so go to a directory where you will store this project and type the following in the terminal

Install create-react-app CLI tool globally:

npm install -g create-react-app

Create a new project with create-react-app by running the following command:

npx create-react-app react-pwa

Creating a React PWA

Creating a PWA requires these two files

  • serviceWorker.json
  • manifest.js

Create React App comes with a configuration that supports PWA, which is generated when we initiate the build script. We can set up our Create React App project as a PWA by accessing the src/index.js file and changing the last line, which will register the serviceWorker:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.register();

Now, when we run the build script, the minified bundle of our application will use the offline/cache first approach. Under the hood, react-scripts uses a package called workbox-webpack-plugin, which works together with webpack 4 to serve our application as a PWA. It doesn’t only cache local assets placed in the public directory; it also caches navigation requests so that our application acts more reliably on unstable mobile networks.

manifest.json

Most of the configuration for our PWA is placed here, which we can see if we open the public/manifest.json file.

{
  "short_name": "React App",
  "name": "Create React App Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

The short_name and name fields describe how our application should be identified to users.
The short_name field should be no longer than 12 characters and will be shown underneath the application icon on the user’s home screen. For the name field, we can use up to 45 characters. This is the main identifier for our application and can be seen during the process of adding the application to the home screen.
The icon field configures the particular icon users see when they add our application to their home screen.
Finally, using the theme_color and background_color fields, we can set the colors (in HEX format) for the top bar when our application is opened from the home screen on a mobile device:

Now that we have setup our PWA environment, let’s build the Github profile tracker project.

Project Overview

Robot army PWA will show a list of robots with images received from an external API. Therefore, we need to fetch the official jsonplaceholder REST API and pull information from its endpoints. We’ll use the fetch API for this. We can retrieve our robot’s data by executing the following command.

curl https://jsonplaceholder.typicode.com/users

API Reference

Our Robot army PWA will fetch robots data from https://jsonplaceholder.typicode.com/users While the robot image will be fetched from https://robohash.org/

Structuring our application

To begin, we’ll create two new directories called components and containers inside the src directory. The container directory will contain only the smart components (i.e component that has state). The files for the App component can be moved to the container directory and the App.test.js file can be deleted since testing will not be covered in this article.

Styling in React with styled-components

Using CSS files to add styling to our React components forces us to import these files across different components, which makes our code less reusable. Therefore, we’ll add the styled-components package to the project, which allows us to write CSS inside JavaScript (so-called CSS-in-JS) and create components. By doing this, we’ll get more flexibility out of styling our components, will be able to prevent duplication or overlapping of styles due to classNames, and we’ll add dynamic styling to components with ease.

All of this can be done using the same syntax we used for CSS, right inside our React components. In this project, all the component styling will be based on styled-components.
The first step is installing styled-components using npm:

npm install styled-components

Header component

Create a new component called Header and add the following code to src/components/Header/Header.js:

import React from 'react';
import styled from 'styled-components';
import logo from '../../GitHub-Mark-Light-64px.png';
const HeaderWrapper = styled.div`
  background-color: #282c34;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
  width: 100%;
`;
const Logo = styled.img`
  height: 64px;
  pointer-events: none;
`;
const Header = () => (
  <HeaderWrapper>
    <Logo src={`https://robohash.org/7?size=200x200`} alt='logo' />
    <h1>My Robot Army</h1>
  </HeaderWrapper>
);
export default Header;

Robot Component

Create a new component called Robot and add the following code to src/components/Robot/Robot.js:

import React from 'react';
import styled from 'styled-components';
const Card = styled.div`
    background-color: blue;
    padding: 1rem;
    text-align: center;
    margin: .5rem;
    width: 20rem;
    justify-content: center;
    &:hover {
        box-shadow: 5px 10px 10px 5px #e6dddd; 
    }
`;
const Image = styled.img`
  height: 4rem;
  width: 4rem;
  border-radius: 2rem;
  pointer-events: none;
  margin-bottom: .8rem;
`;
const Name = styled.p`
    font-size: 1.5rem;
    font-weight: bold;
`;
const Username = styled.p`
    font-size: 1rem;
    font-weight: bold;
`;

const Robot = ({robot}) => (
    <>
        <Card>
            <Image src={`https://robohash.org/${robot.id}?size=200x200`} alt='logo'/>
            <Name>
                {robot.name}
            </Name>
            <Username>
                {robot.username}
            </Username>
        </Card>

    </>
);

export default Robot;

This component recieves the robot data as props, using the props id to fetch the robot image from https://robohash.org

Robots Component

To retrieve our robots information, create a new container called Robots and add the following code to src/containers/Robots/Robots.js:

import React, { Component } from "react";
import styled from "styled-components";
import Robot from "../../components/Robot/Robot";
const Layout = styled.div`
  background-color: white;
  width: 100%;
  height: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
`;
class Robots extends Component {
  constructor() {
    super();
    this.state = {
      robots: [],
      loading: true,
      error: "",
    };
  }
  async componentDidMount() {
    try {
      const robots = await fetch("https://jsonplaceholder.typicode.com/users");
      const robotsJSON = await robots.json();
      if (robotsJSON) {
        this.setState({
          robots: robotsJSON,
          loading: false,
        });
      }
    } catch (error) {
      this.setState({
        loading: false,
        error: error.message,
      });
    }
  }
  render() {
    const { robots, loading, error } = this.state;
    if (loading || error) {
      return <div>{loading ? 'Loading...' : error}</div>;
    }
    return (
      <>
        <Layout>
          {robots.map((robot, key) => (
            <Robot key={key} robot={robot} />
          ))}
        </Layout>
      </>
    );
  }
}
export default Robots;

This new component contains a constructor, where the initial value for state is set and a componentDidMount life cycle method, which is used asynchronously to set a new value for state when the fetched API returns a result.

Now, import the Header and Robots component into the App component:

import React, { Component } from 'react';
import styled, { createGlobalStyle } from 'styled-components';
import Header from '../components/Header/Header';
import Robots from './Robots/Robots';
const GlobalStyle = createGlobalStyle`
  body {
    margin: 0;
    padding: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
      "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
      sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
`;
const AppWrapper = styled.div`
  text-align: center;
  width: 100%;
`;
class App extends Component {
  render() {
    return (
      <>
        <GlobalStyle />
        <AppWrapper>
          <Header />
          <Robots/>
        </AppWrapper>
      </>
    );
  }
}
export default App;

Serving the PWA

With the configuration of our PWA in place, it’s time to see how this will affect the application.
The PWA will only be visible when the build version of our application is open. To do this, execute the following command in the projects’ root directory:

npm run build

This will initiate the build process, which minifies our application to a bundle that’s stored inside the build directory. Create React App suggest how we should serve this build version:

npm install -g serve
serve -s build

The npm install command installs the serve package, which is used to serve built static sites or, in this case, JavaScript applications. After installing this package, we can use it to deploy the build directory on our server or local machine by running the following:

serve -s build

If we visit our project in the browser at http://localhost:5000/, we’ll see that everything looks exactly like the version we’re running on http://localhost:3000/. There is one big difference, however: the build version is running as a PWA. This means that if our internet connections fails, the application will still be shown. We can try this out by disconnecting our internet connection or stopping the serve package from the command line. If we refresh the browser on http://localhost:5000/, we will see the exact same application.
Also, if we visit our live project demo at https://robot-army-pwa.netlify.app/ then disconnect our internet connection and refresh the browser, we will see the exact same application.

Conclusion

That’s it! We have succeeded in building our PWA with react.
In the process, we have learned:

  • What is a PWA
  • How to create a Progressive web application
  • How to serve a progressive web application
  • Styling in React with styled-components

You can find the complete project for this article on Github. Here is the link to the live demo of the project https://robot-army-pwa.netlify.app/. Happy coding.


Share on social media

//