Use React Hooks to Share States Between your Service Workers

Communicating across tabs and windows

Lucas Estevão
Jan 7 · 5 min read
Image for post
Image for post

Building a progressive web app (PWA) usually stands for instant access regardless of network state, but a PWA can offer much more through the use of service workers, including state share across multiple browser tabs and windows.

Sharing the state across tabs can have multiple utilities — for this specific example, let’s think about an e-commerce cart that updates every time we add or remove products. We can efficiently achieve a state share across tabs and windows by combining service workers with React Hooks. Let’s explore that.

Hands-On React

We’ll start declaring the cart counter in our state and create the functions responsible to increment and decrement the counter of products on the bag. You can call these functions on a call-to-action button inside your product component, for instance.

Image for post
Image for post
Step 1: useState and auxiliary functions

Before working with service workers, we must make sure the feature is available in the browser — only then can we safely register our serviceWorker.js. By convention, the service worker's availability is verified with 'serviceWorker' in navigator, but coding otherwise does not affect the results anyhow.

Considering we don’t need any actions during the transition to load the page, we’ll apply the best practice to register the service worker after the page is loaded, thus avoiding any performance gaps.

Our goal here is as soon as the service worker is ready to add a listener to the message event. In such a manner, when the event is triggered and there’s data available, we can update the state of our cart with the value sent through the service worker.

Image for post
Image for post
Step 2: useEffect, serviceWorker register, ready and addEventListener

Note that we need to inject the setCounter function from our state and the sw variable we’ve declared to use the useEffect Hook and update the counter every time a product is added or removed from the bag. In another note, I'm using the optional chaining operator, to supposedly improve readability and leave the code cleaner.

Setting Up the Service Worker

Inside serviceWorker.js, we’ll bind a function to the message event, from where we’ll retrieve the data coming along with the event and the ID of the client (browser tab or window). I'm using the destructuring assignment syntax to unpack only the values I need.

Image for post
Image for post
Step 3: serviceworker.js, clients, matchAll and client postMessage

When the message event is triggered, we’ll call the matchAll method of the Clients interface. This returns a Promise for a list of service-worker Client objects, which may represent a browser tab or window. This will help us get all, and only, the service-worker clients controlled by the service worker we have registered in our app.

Once we have our tabs and windows list (clients), we go through each one and verify if that’s not the current client. In this case, we send a message with the data we received in our event using the postMessage method. This method allows our service worker to send a message to a client. This message, in turn, is received in the message event on navigator.serviceWorker.

At this point, we’re able to connect the action (postMessage) from the service worker file with the listener we have declared in our React app. The next step is to trigger the action from our React app towards our service worker.

Just before we go to our next step, I’d recommend adding skipWaiting to the install event of our service worker. This method forces the waiting service worker to become the active service worker. It also ensures any updates to the underlying service worker will take effect immediately for both the current client and all other active clients, offering a better user experience for our use case.

Image for post
Image for post
Step 3.1: serviceworker.js, install and skipWaiting

Wrapping Up the Solution

When the user adds or removes a product to the bag from one window, they’ll see the state updating immediately on the screen. Now we need a function to tell our service worker to update all other tabs and windows.

We’ll use the postMessage available on the service worker controller, passing the state value as an argument.

The controller is a read-only property of the ServiceWorkerContainer interface and returns a ServiceWorker object, which inherits methods from its parent, Worker, where the postMessage method is available.

Image for post
Image for post
Step 4. controller and worker postMessage

Finally, we call our stateToServiceWorker function inside our very own auxiliary function to decrement and increment the state. The cycle is complete — we can send updates to the service worker and receive from it, keeping multiples tabs and windows with a concise state.

Image for post
Image for post
Step 5: serviceworker.js, install and skipWaiting
Image for post
Image for post

Thank you for reading!

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store