Communicating across tabs and windows
Communicating across tabs and windows | ||
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. | ||
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. | ||
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. | ||
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. | ||
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. | ||
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. | ||
Step 5: serviceworker.js, install and skipWaiting | ||
Resources | ||
Below you can find the complete code of this use case. | ||
GitHub: https://github.com/lucasestevao/react-serviceworker-state-share/tree/master/ | ||
CodeSandbox: https://codesandbox.io/s/github/lucasestevao/react-serviceworker-state-share/tree/master/ | ||
Live Project: https://csb-nxdkl-pjh3c3wz3.now.sh/ | ||
Thank you for reading! | ||
By Lucas Estevão on January 7, 2020. | ||