proposal-signals icon indicating copy to clipboard operation
proposal-signals copied to clipboard

Signal.subtle.watched/unwatched is not sufficient, we need to be notified when a signal is used (even without a watcher)

Open divdavem opened this issue 8 months ago • 5 comments

Hello, As a maintainer of the tansu signal library, I am trying (here) to re-implement it using signal-polyfill and I came across the following difference of behavior, that I would like to solve, avoiding any breaking change.

With tansu (inspired by svelte stores), as described further in this article we have the ability to know whether a signal is used or not, in order to update the signal value and add or remove event listeners. For example, we can create the following signal that contains a string value from the local storage and a computed that parses the value as JSON:

import {readable, computed} from '@amadeus-it-group/tansu';

// this signal is synchronized with the "preferences" item in local storage
const preferences$ = readable(null as string | null, (set) => {
  // this function is called when the signal starts to be used

  const updateFromStorage = (e: StorageEvent) => {
    if (e.key === "preferences") {
      set(e.newValue);
    }
  };
  window.addEventListener('storage', updateFromStorage);
  set(localStorage.getItem("preferences"));
  return () => {
    // this function is called when the signal is no longer used
    window.removeEventListener('storage', updateFromStorage)
  };
});

const parsedPreferences$ = computed(() => JSON.parse(preferences$() ?? "null"));

Now if I call parsedPreferences$() at any time, I have the current value of the "preferences" local storage item parsed as a json object.

However, with the current signals proposal, this is not possible to achieve without constantly listening to the "storage" event.

I know the Signal.subtle.watched and Signal.subtle.unwatched callbacks, but they are not called when the value is read through a one-time access to the value of a dependent signal:

import { Signal } from 'signal-polyfill';

const updateFromStorage = (e: StorageEvent) => {
  if (e.key === "preferences") {
    preferences$.set(e.newValue);
  }
}
const preferences$ = new Signal.State(null as string | null, {
  [Signal.subtle.watched]: () => {
    preferences$.set(localStorage.getItem("preferences"));
    window.addEventListener('storage', updateFromStorage);
  },
  [Signal.subtle.unwatched]: () => {
    window.removeEventListener('storage', updateFromStorage);
  }
})

const parsedPreferences$ = new Signal.Computed(() => JSON.parse(preferences$.get() ?? "null"));

With the above code, if I read parsedPreferences$.get() once (without having a watcher) and then the value of the "preferences" local storage item changes from another tab, and then I read again (still without having a watcher) parsedPreferences$.get(), the value will not be up-to-date.

I believe it is fundamental that the signals proposal allows such a use case.

divdavem avatar Jun 12 '24 09:06 divdavem