DEV Community

Cover image for Using BroadcastChannel API with Vue to sync a ref across multiple tabs
Nico Prat for 365Talents

Posted on

Using BroadcastChannel API with Vue to sync a ref across multiple tabs

We try to save time for everyone in our team, not only developers: some people spend a lot of time configuring our app for clients or demos, so we make sure it's as smooth as possible. For instance, we try to make every change real time, so there's no need to reload the app for changes to appear.

Recently, we figured out those people usually work with multiple tabs open, making sure the configuration works as expected in multiple pages of the app. So we thought about syncing the configuration across tabs.

We didn't want to store it, neither in session, local storage or anything else, as we will then have to make sure it's always up to date.

That's when we came across the BroadcastChannel API, I didn't even know it existed. It's not fairly new, but Safari was the last to implement it according to CanIUse. Anyway it's largely supported now. You can think of it like the good old window.postMessage() from iframe, but across multiple tabs of the same origin.

Luckily for us, VueUse already made a little composable to ease its usage: https://vueuse.org/core/useBroadcastChannel/#usebroadcastchannel

const {
  isSupported,
  channel,
  post,
  close,
  error,
  isClosed,
} = useBroadcastChannel({ name: 'unique-name' })
Enter fullscreen mode Exit fullscreen mode

So we created a little in house composable based on it to make sure a ref is always synchronized among all tabs:

import { useBroadcastChannel, watchPausable } from '@vueuse/core';
import { nextTick, watch } from 'vue';

import type { Ref } from 'vue';

export const useSync = <T>(value: Ref<T>, name: string, options?: { immediate?: boolean; deep?: boolean }) => {
  // Name must be unique
  const { post, data } = useBroadcastChannel<T, T>({ name });

  // When value changes locally, update other tabs
  const { pause, resume } = watchPausable(
    () => value.value,
    (newValue) => {
      post(structuredClone(newValue));
    },
    options,
  );

  // When value changes in another tab, update it locally
  watch(
    () => data.value,
    async (newValue) => {
      // Prevent watch loop when updating config
      pause();
      value.value = newValue;
      await nextTick();
      resume();
    },
    options,
  );
};
Enter fullscreen mode Exit fullscreen mode

So now we can sync a ref with a single line:

const config = ref({})
useSync(config, 'config', { deep: true });
Enter fullscreen mode Exit fullscreen mode

And voilà! That's some hours saved each month across teams 🥳

Top comments (0)