State syncing can be tricky to accomplish with web apps, but it's one of the scenarios improved by using the BroadcastChannel API.
This native web API allows messaging between browsing contexts from the same origin. In other words, if a user has multiple tabs, windows, and even an iframe open from santas-list.tld, use of this API can push messages between all of those contexts. How you handle those messages enables state syncing or other actions.
Sending and Receiving Broadcast Messages
#We're going to create a simple app for Santa and his elves to track children's naughty or nice status.
The first step is to set up a broadcast channel. You can name this whatever fits the purpose of your app, and depending on your needs, you can have one channel or multiple.
const santasList = new BroadcastChannel("santasList");
Next, we can send our first message using the API's postMessage
method. The content can be a string, as shown, or another data type, like an array or object.
santasList.postMessage("Hello World");
We can receive and then respond to the message by adding an event listener to the broadcast channel we set up, listening to the message
event. Any information contained in the message is available in the data
property.
santasList.addEventListener("message", ({ data }) => {
console.log(data);
});
We'll wrap that up in a simple button to test it out. You can do this by opening this article again in a new tab. Then, try pressing the button in one tab and then returning to the other tab to see the message output.
Hello World test message
const santasList = new BroadcastChannel("santasList");
document.getElementById('hello-world').addEventListener('click', () => {
santasList.postMessage("Hello World");
});
santasList.addEventListener('message', ({ data }) => {
const messageBox = document.getElementById('hello-world-message');
messageBox.textContent = data;
});
A couple of things to note:
- The message will only be output in the non-broadcasting tab (or window)
- The message will only sync to currently open contexts, so it will not appear if you open an additional tab (or window)
- The broadcast channel is for real-time messaging and will not persist any data
Demo App: Santa's List
#For our example, a list of names is presented with options for a "Nice" or "Naughty" behavior. To enable Santa and the elves to check the list twice, we must keep the behavior state in sync across browsing contexts.
In other words, if Priscilla's behavior is updated from "Nice" to "Naughty," we want that immediately reflected in any other open tabs to prevent duplicate updates and stale data.
The demo uses all the same methods we've reviewed. However, the logic is extended to update the button state instead of outputting the message.
This app—even as simple as it is—shows just how flexible the API is since you can send data in any shape, and it's entirely up to your application what you do with that data.
You'll see a little extra fluff in the CodePen because I chose to create a web component for managing each list item to isolate sending and receiving messages per item.
The basic logic is that once either behavior is selected for a list item, its aria-pressed
state is toggled. The alternate button is also toggled to the opposite state since the behaviors are mutually exclusive binary choices.
Coupled with the name
data and the id
('nice' or 'naughty') of the pressed button, the following message is broadcast.
santasList.postMessage({
name,
id,
pressed // updated aria-pressed state
});
A custom event is used to pass the data from the message
event listener. This enables a single message
event listener rather than instantiating one per custom element. In the future, we might have additional actions we want to take upon a received message.
const updateListEvent = (detail) =>
document.dispatchEvent(new CustomEvent("update-list", { detail }));
santasList.addEventListener("message", ({ data }) => {
const { name, id, pressed } = data;
updateListEvent({ name, id, pressed });
});
The list item listens for the custom event and checks if its name
data matches, and if so, hands off the data to update the corresponding button state for itself.
document.addEventListener("update-list", ({ detail }) => {
if (detail.name !== name) return;
// `handleListUpdate` updates button state from
// both in-page button presses and broadcasts
handleListUpdate(detail.id, detail.pressed);
});
Here's the CodePen putting it together. Again, to see it in action, duplicate this tab to see the state reflected in both.
Note that since there's no actual data service behind this simple example, and remembering that the BroadcastChannel in and of itself does not persist data, changes made before opening a new tab will not be reflected. This means that, ultimately, you are likely to need some sort of database or storage solution if data changes need to be saved.
Other Opportunities for Use
#Here are a few ways to use the BroadcastChannel API.
Syncing of all kinds of states, including:
- logged in status
- notifications (read, unread, new)
- file or progress saving
- form and uploaded data
- chat messages
- profile details
- store cart changes
You can also use it to take more advanced and involved actions. I became familiar with the API by using it for my custom conference presentation framework (example presentation). I used it to navigate slides from a "notes" view via key presses. I also used messages to trigger custom events to mimic element states like hover and focus, reflect typed text updates, and resize code vs. result panels.
Resources
#Check out the following to learn more about the BroadcastChannel API.
- Broadcast Channel API overview on MDN
- Overview from the Chrome blog, which also reviews differences from other available messaging APIs
- Tutorial with additional examples for use from Christian Nwamba
Stephanie selected The Trevor Project for an honorary donation of $50
The Trevor Project’s mission is to end suicide among LGBTQ young people.