Skip to content

RFC: Expose a qCleanup event when component is unmounted #219

Open
@GrandSchtroumpf

Description

@GrandSchtroumpf

Champion

@GrandSchtroumpf

What's the motivation for this proposal?

Problems you are trying to solve:

  • I want to start listening on event outside of document, window or HTMLElement without downloading core.
  • I want to cleanup listener that started within a useOn.

Goals you are trying to achieve:

Wrap Browser based event emitter, without downloading core right away.
Examples:

// wrap navigation.addEventListener('navigate', ...);
useNavigation('navigate', $(event => ...));

// wrap matchMedia('(min-width: 600px)').addEventListener('change', ...)
useMatchMedia('(min-width: 600px)', $(event => ...))

Any other context or information you want to share:

useOn is the best place to achieve that, but it doesn't cleanup, so we need a way to know when the component is unmounted to cleanup.


Proposed Solution / Feature

What do you propose?

Emit a new CustomEvent before the component unmount.

Code examples

  1. When component is visible, start listeneing on 'navigate' without downloading core.
  2. When the event is triggers, it's forwarded to document.
  3. When document receive event, trigger the logic from the QRL
const useNavigation = (eventName: string, cb: QRL<(event: Event, element: Element) => any>) => {
  useOnDocument('<custom-event>', cb);
  useOn('qVisible', _qrlSync(
    (e, el) => {
      const handler = (event) => document.dispatchEvent(new CustomEvent('<custom-event>', {detail:event}));
      navigation.addEventListener(eventName, handler);
      el.addEventListener('qCleanup', () => navigation.removeEventListener(eventName, handler), { once: true });
    },
    `(e, el) => {
      const handler = (event) => document.dispatchEvent(new CustomEvent('<custom-event>', {detail:event}));
      navigation.addEventListener('${eventName}', handler);
      el.addEventListener('qCleanup', () => navigation.removeEventListener('${eventName}', handler), { once: true });
    }`,
  ));
};

The goal here is to

  • not dowload core too early.
  • not adding too much logic in the inline script (addEventListener and removeEventListener).
  • make sure all event listeners are removed when the component is removed.

Additional information

The qCleanup event should not impact the rendering process (shouldn't wait for the callbacks to fullfil)

In an ideal world the element should be still in the DOM when the event is received. But it's not mandatory for this RFC:

useOn('qCleanup', $(async (ev, el) => {
  console.log(el.isConnected); // Ideally this should be true, but not mandatory
  await new Promise((res) => setTimeout(res, 100));
  console.log(el.isConnected); // This should be false
})

Another usage of this event would be to run ViewTransition on leaving element (But it would only work if the el is still in the DOM at this moment...)

useOn('qCleanup', $(async (ev, el) => {
  const name = `_${Math.random()}_`;
  el.style.viewTransitionName = name;
  const transition = startViewTransition(() => {
    return new Promise((res) => getPlatform().nextTick(res));
  });
  await transition.ready;
  document.documentElement.animate({ opacity: 0 }, {
    pseudoElement: `view-transition-old(${name})`,
    duration: 100
  })
}))

PRs/ Links / References

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    In Progress (STAGE 2)

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions