React (without Redux) - JWT Authentication Tutorial & Example

React (without Redux) - JWT Authentication Tutorial & Example

In this tutorial we'll go through an example of how you can implement JWT authentication in React (without Redux). I posted another version a while back with redux and thought it would be helpful to post an updated version showing how it can be done without redux.

In this tutorial we'll go through an example of how you can implement JWT authentication in React (without Redux). I posted another version a while back with redux and thought it would be helpful to post an updated version showing how it can be done without redux.

The tutorial example is pretty minimal and contains just 2 pages to demonstrate JWT authentication in React - a login page and a secure home page.

Running the React JWT Authentication Example Locally

The tutorial example uses Webpack 4 to transpile the React code and bundle the modules together, and the webpack dev server is used as the local web server, to learn more about using webpack you can check out the official webpack docs.

  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/react-jwt-authentication-example
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the command line in the project root folder.
  5. Your browser should automatically open at [http://localhost:8080](http://localhost:8080) with the login page of the demo React JWT authentication app displayed.
Running the Tutorial Example with a Real Backend API

The React JWT authentication example app uses a fake / mock backend by default so it can run in the browser without a real api, to switch to a real backend api you just have to remove or comment out the 2 lines below the comment // setup fake backend located in the /src/index.jsx file.

React JWT Authentication Project Structure

All source code for the React JWT authentication tutorial is located in the /src folder. Inside the src folder there is a folder per feature (App, HomePage, LoginPage) and few folders for non-feature code that can be shared across different parts of the app (_components, _helpers, _services).

I prefixed non-feature folders with an underscore "_" to group them together and make it easy to distinguish between features and non-features, it also keeps the project folder structure shallow so it's quick to see everything at a glance from the top level and to navigate around the project.

The index.js files in each folder are barrel files that group the exported modules from a folder together so they can be imported using the folder path instead of the full module path and to enable importing multiple modules in a single import (e.g. import { userService, authenticationService } from '@/_services').

A path alias '@' has been configured in the webpack.config.js that maps to the '/src' directory. This allows imports to be relative to the '/src' folder by prefixing the import path with '@', removing the need to use long relative paths like import MyComponent from '../../../MyComponent'.

Click any of the below links to jump down to a description of each file along with it's code:

React Tutorial Components Folder

Path: /src/_components

The _components folder contains shared React components that can be used anywhere in the application.

React Tutorial Private Route Component

Path: /src/_components/PrivateRoute.jsx

The react private route component renders a route component if the user is logged in, if the user isn't logged in they are redirected to the /login page.

import React from 'react';
import { Route, Redirect } from 'react-router-dom';

import { authenticationService } from '@/_services';

export const PrivateRoute = ({ component: Component, ...rest }) => (
     {
        const currentUser = authenticationService.currentUserValue;
        if (!currentUser) {
            // not logged in so redirect to login page with the return url
            return 
        }

        // authorised so return component
        return 
    }} />
)
React Tutorial Helpers Folder

Path: /src/_helpers

The helpers folder contains all the bits and pieces that don't fit into other folders but don't justify having a folder of their own.

React Tutorial Auth Header

Path: /src/_helpers/auth-header.js

Auth header is a helper function that returns an HTTP Authorization header containing the JWT auth token of the currently logged in user. If the user isn't logged in an empty object is returned.

The auth header is used to make authenticated HTTP requests to the server api using JWT authentication.

import { authenticationService } from '@/_services';

export function authHeader() {
    // return authorization header with jwt token
    const currentUser = authenticationService.currentUserValue;
    if (currentUser && currentUser.token) {
        return { Authorization: `Bearer ${currentUser.token}` };
    } else {
        return {};
    }
}
React Tutorial Fake / Mock Backend

Path: /src/_helpers/fake-backend.js

The fake backend enables the example to run without a backend (backend-less), it contains a hardcoded collection of users and provides fake implementations for the api endpoints "authenticate" and "get all users", these would be handled by a real api and database in a production application.

The "authenticate" endpoint is used for logging in to the application and is publicly accessible, the "get all users" endpoint is restricted to users that are logged in.

The fake backend is implemented by monkey patching the fetch() function to intercept certain api requests and mimic the behaviour of a real api. Any requests that aren't intercepted get passed through to the real fetch() function.

export function configureFakeBackend() {
    let users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];
    let realFetch = window.fetch;
    window.fetch = function (url, opts) {
        const isLoggedIn = opts.headers['Authorization'] === 'Bearer fake-jwt-token';

        return new Promise((resolve, reject) => {
            // wrap in timeout to simulate server api call
            setTimeout(() => {
                // authenticate - public
                if (url.endsWith('/users/authenticate') && opts.method === 'POST') {
                    const params = JSON.parse(opts.body);
                    const user = users.find(x => x.username === params.username && x.password === params.password);
                    if (!user) return error('Username or password is incorrect');
                    return ok({
                        id: user.id,
                        username: user.username,
                        firstName: user.firstName,
                        lastName: user.lastName,
                        token: 'fake-jwt-token'
                    });
                }

                // get users - secure
                if (url.endsWith('/users') && opts.method === 'GET') {
                    if (!isLoggedIn) return unauthorised();
                    return ok(users);
                }

                // pass through any requests not handled above
                realFetch(url, opts).then(response => resolve(response));

                // private helper functions

                function ok(body) {
                    resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(body)) })
                }

                function unauthorised() {
                    resolve({ status: 401, text: () => Promise.resolve(JSON.stringify({ message: 'Unauthorised' })) })
                }

                function error(message) {
                    resolve({ status: 400, text: () => Promise.resolve(JSON.stringify({ message })) })
                }
            }, 500);
        });
    }
}
React Tutorial Handle Response

Path: /src/_helpers/handle-response.js

The handleResponse function checks responses from the api to see if the request was unauthorised, forbidden or unsuccessful.

If the response status is 401 Unauthorized or 403 Forbidden then the user is automatically logged out of the application, this handles if the user token is no longer valid for any reason. If the response contains an error then a rejected promise is returned that includes the error message, otherwise if the request was successful then the response data is returned as a JSON object.

import { authenticationService } from '@/_services';

export function handleResponse(response) {
    return response.text().then(text => {
        const data = text && JSON.parse(text);
        if (!response.ok) {
            if ([401, 403].indexOf(response.status) !== -1) {
                // auto logout if 401 Unauthorized or 403 Forbidden response returned from api
                authenticationService.logout();
                location.reload(true);
            }

            const error = (data && data.message) || response.statusText;
            return Promise.reject(error);
        }

        return data;
    });
}
React Tutorial History

Path: /src/_helpers/history.js

The history is a custom history object used by the React Router, the reason I used a custom history object instead of the one built into React Router is to enable redirecting users from outside React components, for example in the logout method of the App component.

import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();
React Tutorial Services Folder

Path: /src/_services

The _services layer handles all http communication with backend apis for the application, each service encapsulates the api calls for a content type (e.g. users) and exposes methods for performing various operations (e.g. CRUD operations). Services can also have methods that don't wrap http calls, for example the authenticationService.logout() method just removes the currentUser object from localStorage and sets it to null in the application.

I like wrapping http calls and implementation details in a services layer because it provides a clean separation of concerns and simplifies the react components that use the services.

React Tutorial Authentication Service

Path: /src/_services/authentication.service.js

The authentication service is used to login and logout of the application, to login it posts the user's credentials to the /users/authenticate route on the api, if authentication is successful the user details including the token are added to local storage, and the current user is set in the application by calling currentUserSubject.next(user);.

RxJS subjects and observables are used by the service to store the current user state and communicate between different components in the application.

The logged in user details are stored in local storage so the user will stay logged in if they refresh the browser and also between browser sessions until they explicitly logout. If you don't want the user to stay logged in between refreshes or sessions the behaviour could easily be changed by storing user details somewhere less persistent such as session storage which would persist between refreshes but not browser sessions, or you could remove the calls to localStorage which would cause the user to be logged out if the browser is refreshed.

There are two properties exposed by the authentication service for accessing the currently logged in user. The currentUser observable can be used when you want a component to reactively update when a user logs in or out, for example in the App.jsx component so it can show/hide the main nav bar when the user logs in/out. The currentUserValue property can be used when you just want to get the current value of the logged in user but don't need to reactively update when it changes, for example in the PrivateRoute.jsx component which restricts access to routes by checking if the user is currently logged in.

import { BehaviorSubject } from 'rxjs';

import config from 'config';
import { handleResponse } from '@/_helpers';

const currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser')));

export const authenticationService = {
    login,
    logout,
    currentUser: currentUserSubject.asObservable(),
    get currentUserValue () { return currentUserSubject.value }
};

function login(username, password) {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    };

    return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
        .then(handleResponse)
        .then(user => {
            // store user details and jwt token in local storage to keep user logged in between page refreshes
            localStorage.setItem('currentUser', JSON.stringify(user));
            currentUserSubject.next(user);

            return user;
        });
}

function logout() {
    // remove user from local storage to log user out
    localStorage.removeItem('currentUser');
    currentUserSubject.next(null);
}
React Tutorial User Service

Path: /src/_services/user.service.js

The user service contains just a couple of methods for retrieving user data from the api, it acts as the interface between the Angular application and the backend api.

I included the user service to demonstrate accessing secure api endpoints with the http authorization header set after logging in to the application, the auth header is set with a JWT token in the auth-header.js helper above. The secure endpoints in the example are fake/mock routes implemented in the fake-backend.js helper above.

import config from 'config';
import { authHeader, handleResponse } from '@/_helpers';

export const userService = {
    getAll
};

function getAll() {
    const requestOptions = { method: 'GET', headers: authHeader() };
    return fetch(`${config.apiUrl}/users`, requestOptions).then(handleResponse);
}
React Tutorial App Folder

Path: /src/App

The app folder is for react components and other code that is used only by the app component in the tutorial application.

React Tutorial App Component

Path: /src/App/App.jsx

The app component is the root component for the react tutorial example, it contains the outer html, routes and main nav bar for the example app.

It subscribes to the currentUser observable in the authentication service so it can reactively show/hide the main navigation bar when the user logs in/out of the application. I didn't worry about unsubscribing from the observable here because it's the root component of the application, so the only time the component will be destroyed is when the application is closed which would destroy any subscriptions as well.

The app component contains a logout() method which is called from the logout link in the main nav bar to log the user out and redirect them to the login page.

import React from 'react';
import { Router, Route, Link } from 'react-router-dom';

import { history } from '@/_helpers';
import { authenticationService } from '@/_services';
import { PrivateRoute } from '@/_components';
import { HomePage } from '@/HomePage';
import { LoginPage } from '@/LoginPage';

class App extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            currentUser: null
        };
    }

    componentDidMount() {
        authenticationService.currentUser.subscribe(x => this.setState({ currentUser: x }));
    }

    logout() {
        authenticationService.logout();
        history.push('/login');
    }

    render() {
        const { currentUser } = this.state;
        return (
            
                
                    {currentUser &&
                        
                            
                                Home
                                Logout
                            
                        
                    }
                    
                        
                            
                                
                                    
                                    
                                
                            
                        
                    
                
            
        );
    }
}

export { App };
React Tutorial Home Page Folder

Path: /src/HomePage

The home page folder is for react components and other code that is used only by the home page component in the tutorial application.

React Tutorial Home Page Component

Path: /src/HomePage/HomePage.jsx

The home page component is displayed after signing in to the application, it contains a simple welcome message and a list of all users. The component gets the current user from the authentication service and then fetches all users from the api by calling the userService.getAll() method from the componentDidMount() react lifecycle hook.

import React from 'react';

import { userService, authenticationService } from '@/_services';

class HomePage extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            currentUser: authenticationService.currentUserValue,
            users: null
        };
    }

    componentDidMount() {
        userService.getAll().then(users => this.setState({ users }));
    }

    render() {
        const { currentUser, users } = this.state;
        return (
            
                # Hi {currentUser.firstName}!

                
You're logged in with React & JWT!!

                ### Users from secure api end point:

                {users &&
                    
                        {users.map(user =>
                            {user.firstName} {user.lastName}
                        )}
                    
                }
            
        );
    }
}

export { HomePage };
React Tutorial Login Page Folder

Path: /src/LoginPage

The login page folder is for react components and other code that is used only by the login page component in the tutorial application.

React Tutorial Login Page Component

Path: /src/LoginPage/LoginPage.jsx

The login page component contains a login form with username and password fields. It displays validation messages for invalid fields when the user attempts to submit the form or when a field is touched. If the form is valid the component calls the authenticationService.login(username, password) method, if login is successful the user is redirected back to the original page they were trying to access.

I built the login form using Formik, a higher order component that helps with managing form state, validation, error messages, and form submission. Validation is done with the Yup object schema validator which hooks into Formik via the handy validationSchema prop.

import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';

import { authenticationService } from '@/_services';

class LoginPage extends React.Component {
    constructor(props) {
        super(props);

        // redirect to home if already logged in
        if (authenticationService.currentUserValue) { 
            this.props.history.push('/');
        }
    }

    render() {
        return (
            
                
                    Username: test

                    Password: test
                
                ## Login

                <Formik
                    initialValues={{
                        username: '',
                        password: ''
                    }}
                    validationSchema={Yup.object().shape({
                        username: Yup.string().required('Username is required'),
                        password: Yup.string().required('Password is required')
                    })}
                    onSubmit={({ username, password }, { setStatus, setSubmitting }) => {
                        setStatus();
                        authenticationService.login(username, password)
                            .then(
                                user => {
                                    const { from } = this.props.location.state || { from: { pathname: "/" } };
                                    this.props.history.push(from);
                                },
                                error => {
                                    setSubmitting(false);
                                    setStatus(error);
                                }
                            );
                    }}
                    render={({ errors, status, touched, isSubmitting }) => (
                        
                            
                                Username
                                
                                
                            
                            
                                Password
                                
                                
                            
                            
                                Login
                                {isSubmitting &&
                                    ![](data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==)
                                }
                            
                            {status &&
                                {status}

                            }
                        
                    )}
                />
            
        )
    }
}

export { LoginPage };
React Tutorial Index HTML File

Path: /src/index.html

The base index html file contains the outer html for the whole tutorial application. When the app is started with npm start, Webpack bundles up all of the react code into a single javascript file and injects it into the body of the page.




    
    React - JWT Authentication Tutorial & Example

    
    

    


    



React Tutorial Main Entry File

Path: /src/index.jsx

The root index.jsx file bootstraps the react tutorial application by rendering the App component into the app div element defined in the base index html file above.

The boilerplate application uses a fake / mock backend by default, to switch to a real backend api simply remove the fake backend code below the comment // setup fake backend.

import React from 'react';
import { render } from 'react-dom';

import { App } from './App';

// setup fake backend
import { configureFakeBackend } from './_helpers';
configureFakeBackend();

render(
    ,
    document.getElementById('app')
);
React Tutorial Babel RC (Run Commands)

Path: /.babelrc

The babel config file defines the presets used by babel to transpile the React and ES6 code. The babel transpiler is run by webpack via the babel-loader module configured in the webpack.config.js file below.

{
    "presets": [
        "@babel/preset-react",
        "@babel/preset-env"
    ]
}
React Tutorial Package.json

Path: /package.json

The package.json file contains project configuration information including package dependencies which get installed when you run npm install. Full documentation is available on the npm docs website.

{
    "name": "react-jwt-authentication-example",
    "version": "1.0.0",
    "repository": {
        "type": "git",
        "url": "https://github.com/cornflourblue/react-jwt-authentication-example.git"
    },
    "license": "MIT",
    "scripts": {
        "start": "webpack-dev-server --open"
    },
    "dependencies": {
        "formik": "^1.5.2",
        "history": "^4.9.0",
        "react": "^16.8.6",
        "react-dom": "^16.8.6",
        "react-router-dom": "^5.0.0",
        "rxjs": "^6.3.3",
        "yup": "^0.27.0"
    },
    "devDependencies": {
        "@babel/core": "^7.4.3",
        "@babel/preset-env": "^7.4.3",
        "@babel/preset-react": "^7.0.0",
        "babel-loader": "^8.0.5",
        "html-webpack-plugin": "^3.2.0",
        "path": "^0.12.7",
        "webpack": "^4.29.6",
        "webpack-cli": "^3.3.0",
        "webpack-dev-server": "^3.2.1"
    }
}
React Tutorial Webpack Config

Path: /webpack.config.js

Webpack is used to compile and bundle all the project files so they're ready to be loaded into a browser, it does this with the help of loaders and plugins that are configured in the webpack.config.js file. For more info about webpack check out the webpack docs.

The webpack config file also defines a global config object for the application using the externals property, you can also use this to define different config variables for your development and production environments.

var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
    mode: 'development',
    resolve: {
        extensions: ['.js', '.jsx']
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader'
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.jsx'],
        alias: {
            '@': path.resolve(__dirname, 'src/'),
        }
    },
    plugins: [new HtmlWebpackPlugin({
        template: './src/index.html'
    })],
    devServer: {
        historyApiFallback: true
    },
    externals: {
        // global app config object
        config: JSON.stringify({
            apiUrl: 'http://localhost:4000'
        })
    }
}

The project is available on GitHub at https://github.com/cornflourblue/react-jwt-authentication-example.

Hire Dedicated eCommerce Web Developers | Top eCommerce Web Designers

Hire Dedicated eCommerce Web Developers | Top eCommerce Web Designers

Build your eCommerce project by hiring our expert eCommerce Website developers. Our Dedicated Web Designers develop powerful & robust website in a short span of time.

Build your eCommerce project by hiring our expert eCommerce Website developers. Our Dedicated Web Designers develop powerful & robust website in a short span of time.

Hire Now: https://bit.ly/394wdOx

JavaScript developers should you be using Web Workers?

JavaScript developers should you be using Web Workers?

Do you think JavaScript developers should be making more use of Web Workers to shift execution off of the main thread?

Originally published by David Gilbertson at https://medium.com

So, Web Workers. Those wonderful little critters that allow us to execute JavaScript off the main thread.

Also known as “no, you’re thinking of Service Workers”.

Photo by Caleb Jones on Unsplash

Before I get into the meat of the article, please sit for a lesson in how computers work:

Understood? Good.

For the red/green colourblind, let me explain. While a CPU is doing one thing, it can’t be doing another thing, which means you can’t sort a big array while a user scrolls the screen.

This is bad, if you have a big array and users with fingers.

Enter, Web Workers. These split open the atomic concept of a ‘CPU’ and allow us to think in terms of threads. We can use one thread to handle user-facing work like touch events and rendering the UI, and different threads to carry out all other work.

Check that out, the main thread is green the whole way through, ready to receive and respond to the gentle caress of a user.

You’re excited (I can tell), if we only have UI code on the main thread and all other code can go in a worker, things are going to be amazing (said the way Oprah would say it).

But cool your jets for just a moment, because websites are mostly about the UI — it’s why we have screens. And a lot of a user’s interactions with your site will be tapping on the screen, waiting for a response, reading, tapping, looking, reading, and so on.

So we can’t just say “here’s some JS that takes 20ms to run, chuck it on a thread”, we must think about where that execution time exists in the user’s world of tap, read, look, read, tap…

I like to boil this down to one specific question:

Is the user waiting anyway?

Imagine we have created some sort of git-repository-hosting website that shows all sorts of things about a repository. We have a cool feature called ‘issues’. A user can even click an ‘issues’ tab in our website to see a list of all issues relating to the repository. Groundbreaking!

When our users click this issues tab, the site is going to fetch the issue data, process it in some way — perhaps sort, or format dates, or work out which icon to show — then render the UI.

Inside the user’s computer, that’ll look exactly like this.

Look at that processing stage, locking up the main thread even though it has nothing to do with the UI! That’s terrible, in theory.

But think about what the human is actually doing at this point. They’re waiting for the common trio of network/process/render; just sittin’ around with less to do than the Bolivian Navy.

Because we care about our users, we show a loading indicator to let them know we’ve received their request and are working on it — putting the human in a ‘waiting’ state. Let’s add that to the diagram.

Now that we have a human in the picture, we can mix in a Web Worker and think about the impact it will have on their life:

Hmmm.

First thing to note is that we’re not doing anything in parallel. We need the data from the network before we process it, and we need to process the data before we can render the UI. The elapsed time doesn’t change.

(BTW, the time involved in moving data to a Web Worker and back is negligible: 1ms per 100 KB is a decent rule of thumb.)

So we can move work off the main thread and have a page that is responsive during that time, but to what end? If our user is sitting there looking at a spinner for 600ms, have we enriched their experience by having a responsive screen for the middle third?

No.

I’ve fudged these diagrams a little bit to make them the gorgeous specimens of graphic design that they are, but they’re not really to scale.

When responding to a user request, you’ll find that the network and DOM-manipulating part of any given task take much, much longer than the pure-JS data processing part.

I saw an article recently making the case that updating a Redux store was a good candidate for Web Workers because it’s not UI work (and non-UI work doesn’t belong on the main thread).

Chucking the data processing over to a worker thread sounds sensible, but the idea struck me as a little, umm, academic.

First, let’s split instances of ‘updating a store’ into two categories:

  1. Updating a store in response to a user interaction, then updating the UI in response to the data change
  2. Not that first one

If the first scenario, a user taps a button on the screen — perhaps to change the sort order of a list. The store updates, and this results in a re-rendering of the DOM (since that’s the point of a store).

Let me just delete one thing from the previous diagram:

In my experience, it is rare that the store-updating step goes beyond a few dozen milliseconds, and is generally followed by ten times that in DOM updating, layout, and paint. If I’ve got a site that’s taking longer than this, I’d be asking questions about why I have so much data in the browser and so much DOM, rather than on which thread I should do my processing.

So the question we’re faced with is the same one from above: the user tapped something on the screen, we’re going to work on that request for hopefully less than a second, why would we want to make the screen responsive during that time?

OK what about the second scenario, where a store update isn’t in response to a user interaction? Performing an auto-save, for example — there’s nothing more annoying than an app becoming unresponsive doing something you didn’t ask it to do.

Actually there’s heaps of things more annoying than that. Teens, for example.

Anyhoo, if you’re doing an auto-save and taking 100ms to process data client-side before sending it off to a server, then you should absolutely use a Web Worker.

In fact, any ‘background’ task that the user hasn’t asked for, or isn’t waiting for, is a good candidate for moving to a Web Worker.

The matter of value

Complexity is expensive, and implementing Web Workers ain’t cheap.

If you’re using a bundler — and you are — you’ll have a lot of reading to do, and probably npm packages to install. If you’ve got a create-react-app app, prepare to eject (and put aside two days twice a year to update 30 different packages when the next version of Babel/Redux/React/ESLint comes out).

Also, if you want to share anything fancier than plain data between a worker and the main thread you’ve got some more reading to do (comlink is your friend).

What I’m getting at is this: if the benefit is real, but minimal, then you’ve gotta ask if there’s something else you could spend a day or two on with a greater benefit to your users.

This thinking is true of everything, of course, but I’ve found that Web Workers have a particularly poor benefit-to-effort ratio.

Hey David, why you hate Web Workers so bad?

Good question.

This is a doweling jig:

I own a doweling jig. I love my doweling jig. If I need to drill a hole into the end of a piece of wood and ensure that it’s perfectly perpendicular to the surface, I use my doweling jig.

But I don’t use it to eat breakfast. For that I use a spoon.

Four years ago I was working on some fancy animations. They looked slick on a fast device, but janky on a slow one. So I wrote fireball-js, which executes a rudimentary performance benchmark on the user’s device and returns a score, allowing me to run my animations only on devices that would render them smoothly.

Where’s the best spot to run some CPU intensive code that the user didn’t request? On a different thread, of course. A Web Worker was the correct tool for the job.

Fast forward to 2019 and you’ll find me writing a routing algorithm for a mapping application. This requires parsing a big fat GeoJSON map into a collection of nodes and edges, to be used when a user asks for directions. The processing isn’t in response to a user request and the user isn’t waiting on it. And so, a Web Worker is the correct tool for the job.

It was only when doing this that it dawned on me: in the intervening quartet of years, I have seen exactly zero other instances where Web Workers would have improved the user experience.

Contrast this with a recent resurgence in Web Worker wonderment, and combine that contrast with the fact that I couldn’t think of anything else to write about, then concatenate that combined contrast with my contrarian character and you’ve got yourself a blog post telling you that maybe Web Workers are a teeny-tiny bit overhyped.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

An Introduction to Web Workers

JavaScript Web Workers: A Beginner’s Guide

Using Web Workers to Real-time Processing

How to use Web Workers in Angular app

Using Web Workers with Angular CLI


Why ReactJS is better for Web Application Development?

Why ReactJS is better for Web Application Development?

Web Application Development is the point of contact for a business in today's digital era. It is important to choose the right platform for Web Application Development to build a high end Web

Web Application Development is essential for a business in today’s digital era. Finding the right platform for Web Application Development is important for building an effective Web Application that can enhance the overall customer engagement. Here’s what makes ReactJS a better option for building your next Web Application.