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

Teardown for effects? #3

Open
mindplay-dk opened this issue Nov 21, 2024 · 0 comments
Open

Teardown for effects? #3

mindplay-dk opened this issue Nov 21, 2024 · 0 comments

Comments

@mindplay-dk
Copy link

Quick question, how come Raj doesn't have a controlled way to ensure teardown after effects?

For example, in this section of the docs, the beepEverySecond function relies on shared (effectively global) state to "entangle" two functions, and the teardown (cancel) is then manually integrated into the done callback - this approach seems rather fragile?

Calling effect anywhere but init, or forgetting to call cancel, would both leave dangling timers.

so, instead of this pattern for effects and cleanup...

export function beepEverySecond () {
  let intervalId
  return {
    effect (dispatch) {
      intervalId = setInterval(() => {
        dispatch('beep')
      }, 1000)
    },
    cancel () {
      if (intervalId) {
        clearInterval(intervalId)
      }
    }
  }
}

why can't we have something closer to...

export function beepEverySecond() {
  let intervalId
  return (dispatch) => {
    intervalId = setInterval(() => {
      dispatch('beep');
    }, 1000);

    return () => {
      clearInterval(intervalId);
    }
  }
}

export default {
  init: [0, beepEverySecond], // start beeping
  update (message, state, cancel) {
    switch (message) {
      case 'increment': return [state + 1]
      case 'decrement': return [state - 1]
      case 'reset': return [0]
      case 'beep': return [-state, cancel(beepEverySecond)] // end beeping
    }
  },
  view (state, dispatch) {
    return <div>
      <p>Count is {state}.</p>
      <button onClick={() => dispatch('increment')}>Increment</button>
      <button onClick={() => dispatch('decrement')}>Decrement</button>
      <button onClick={() => dispatch('reset')}>Reset</button>
    </div>
  }
}

Things with global effects (timers, fetch calls, DOM event listeners, etc.) would have a transactional guarantee, e.g. similar to useEffect in React, effects in SolidJS, and so on - the done function would automatically tear down any previous effects, and re-running the same effect would tear down the previous effect first.

In this somewhat peculiar example, I've introduced a cancel function, which would produce an effect that manually cleans up a previous effect, if you want to cancel an effect prematurely for some reason - I kind of feel like there might be a simpler and cleaner way to do that, and perhaps beepEverySecond shouldn't really be an effect in this peculiar example.

This approach does create a problem when the effects are anonymous functions - since they don't have a stable identity, the framework couldn't know when the same effect is being called. There's probably a "more Raj" approach to this that I haven't figured out?

But it feels like something that could be improved in this guide?

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

1 participant