Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to unwatch #97

Open
IsaacLehman opened this issue Jan 24, 2024 · 2 comments
Open

How to unwatch #97

IsaacLehman opened this issue Jan 24, 2024 · 2 comments

Comments

@IsaacLehman
Copy link

Hey!

Really loving ArrowJS - super easy to work with :)

One issue I have found is no way to "un watch". Take for example:

  • You have some component with an onMount. When mounted you setup a watch on some reactive variable.
  • When the component get's disposed of, that watch keeps running.

It would be really helpful if the watch returned some sort of reference which we could then remove on unmount.

A common time this happens is with multi-page apps. When spanning between pages, elements are constantly getting added and removed from the dom. After a bit you just have a bunch of old watch's sitting out there.

Any suggestions are appreciated!
Thanks!

@IsaacLehman
Copy link
Author

Hey @justin-schroeder !

Had some time today so played around with ways to add in a cleanup reference for watch. Appreciate any feedback you may have.

I believe this edit to the reactive.ts file would allow cleaning up a watch function as needed:

/**
 * Watch a function and track any reactive dependencies on it, re-calling it if
 * those dependencies are changed. Returns a function to unwatch.
 * @param  {CallableFunction} fn
 * @param  {CallableFunction} after?
 * @returns [unknown, () => void]
 */
export function w<
  T extends (...args: any[]) => unknown,
  F extends (...args: any[]) => any | undefined
>(fn: T, after?: F): [F extends undefined ? ReturnType<T> : ReturnType<F>, () => void] {
  const trackingId = Symbol()
  // Ensure a new dependency tracker for this watch instance
  if (!dependencyCollector.has(trackingId)) {
    dependencyCollector.set(trackingId, new Map())
  }
  let currentDeps: Map<ReactiveProxy<DataSource>, Set<DataSourceKey>> = new Map()
  const queuedCallFn = queue(callFn)

  function callFn() {
    // Reset dependencies for this call
    dependencyCollector.set(trackingId, new Map())
    const value: unknown = fn()
    const newDeps = dependencyCollector.get(trackingId) as Map<ReactiveProxy<DataSource>, Set<DataSourceKey>>
    // Clean up after getting the new dependencies
    dependencyCollector.delete(trackingId)
    // Remove old observers
    currentDeps.forEach((propertiesToUnobserve, proxy) => {
      propertiesToUnobserve.forEach((prop) => proxy.$off(prop, queuedCallFn))
    })
    // Add new observers
    newDeps.forEach((properties, proxy) => {
      properties.forEach((prop) => proxy.$on(prop, queuedCallFn))
    })
    // Update the current dependencies
    currentDeps = newDeps
    return after ? after(value) : value
  }

  // Setup initial call and observers
  const result = callFn()

  // Setup unwatchfunction
  function unwatch() {
    // Remove all observers set by this watch
    currentDeps.forEach((propertiesToUnobserve, proxy) => {
      propertiesToUnobserve.forEach((prop) => proxy.$off(prop, queuedCallFn))
    })
    // Optional: If the watched function itself is reactive, remove this callback
    if (isReactiveFunction(fn)) fn.$off(callFn)
  }

  // If the function is reactive, setup to re-run on reactive function changes
  if (isReactiveFunction(fn)) fn.$on(callFn)

  // Return the result of the initial function call and the cleanup function
  return [result, unwatch];
}

Basically it would then return both the initial call result and a reference to the clean up function.

Example usage:

let unwatch;
onMount(() => {
  // Setup the watch
  const [watchResult, unwatch] = w(someReactiveFunction);

  // Do something with watchResult if needed
});

onUnmount(() => {
  // Clean up when the component unmounts
  unwatch();
});

@justin-schroeder
Copy link
Owner

I agree, this seems like it would do the ticket. It is a significant breaking change too however — which will probably be required anyway whenever I get around to implementing something like this. There are a number of refactors that have been worked on that havent yet seen the light of day, but as soon as I get more time to tackle this unwatch will be a high priority!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants