3 ms·
This is IMO a great example for React and declarative programming. > There's no special concept to understand By not understanding that you need to cleanup ti
by SebastianKra 2y ago
This is IMO a great example for React and declarative programming.
> There's no special concept to understand
By not understanding that you need to cleanup timers when your view unmounts, you've introduced a subtle bug that your coworker will discover in 6 months when users report that the counter sometimes increases twice as fast.
I've worked on Angular-like apps before, and storing and cleaning up timers was always a pain point:
private timer
private someValue = 0
onMount() {
timer = setTimeout(() => {
someValue++;
setContent(<p id="some-element">value is {someValue}</p>)
}, 1000)
}
onUnmount() {
clearTimeout(timer)
}
React on the other hand, clearly defines the concept of an effect as something that is initiated after the component renders and then cleaned up. You also keep the setup and cleanup close together, so you don't need to manually store the timer handle.
const [someValue, setSomeValue] = useState(0)
useEffect(() => {
const timeout = setTimeout(() => {
setSomeValue(v => v + 1)
}, 1000)
return () => clearTimeout(timeout)
}, [])
return <p>value is {someValue}</p>
People often criticise useEffect for being hard to grasp, but it is the perfect essence of how to manage, well... effects in a component-based system.
That's why Vue and Svelte have the same thing:
https://vuejs.org/guide/essentials/watchers.html https://vuejs.org/guide/essentials/watchers.html
https://svelte.dev/blog/runes https://svelte.dev/blog/runes
- Spivak 2y ago> By not understanding that you need to cleanup timers when your view unmounts I think this library eschews the entire concept of mounting so that point is kind of moot. Will you likely re-invent it again in any non-trivial app, sure, but when you're writing imperative JS it's probably a MPA where code runs top to bottom. Clean up will happen when everything gets thrown away on navigation.
- danielvaughn 2y agoYep, and if you are writing an SPA where cleanup is an issue, personally I think a web component would be a better fit. It's native to the platform and offers lifecycle hooks out of the box. I'm currently playing around with how my library might be used for web components.
- WorldMaker 2y agoI appreciate the RxJS observables approach with similar built-in cleanup, but also ways to build higher-level tools and compose things together. interval(1000).pipe(…)… Puts the timer up front and center. There's some confusion between "hot" and "cold" to deal and sharing with, but the pipelines eventually clean themselves up and the higher-level operators can be very nice to you if you let them.
- SebastianKra 2y agoreactiveX will always have a special place in my heart, but at some point I had to accept that the soup of switchMap, combineLatest, distinctUntilChanged wasn't fun for anyone on the team but me. If we ever get native stream support in JS, I believe it will be closer to hooks than to rxjs. Signals could actually be a good middle-ground with how they are pull-based, automatically infer dependencies, don't require complicated Syntax like switchMap and combineLatest, and give more control to the consumer (batching, memoization).
- WorldMaker 2y agoPersonally, I have a hard time not seeing Signals as just non-conforming Observables missing a bunch of useful operators. They remind me of how much people loved ko.computed() from Knockout (and I've joked at times that Signals are just a return to ko.computed), but then had an awful time debugging the edge cases or fixing the performance from bad dependencies. Hooks maybe lean too much the other side of the spectrum in trying to force manual dependency tracking but maybe making it harder than necessary. ReactiveX-style Observables hit a sweet spot for me in dependency management that is hard to beat, especially once you get into all of those complicated operators like switchMap/combineLatest/distinctUntilChanged that can be very powerful tools in the right hands, especially for things like dependency management and throttling. (Though yes, getting junior developers and some types of teams all on the same page can be the hard part. In general a lot of functional programming gets tot be that way: once over the learning curve it's all gravy, but the learning curve looks like a terrifying roller coaster to many that haven't yet.) A proper pipe operator or a C#-like "fluent extension methods" approach might help a lot to clean up some of "scariness" of the first hill of the roller-coaster. If we are wishing for ponies, it might even be really nice in JS to have a proper monadic do-notation, even just a limited form like C#'s LINQ syntax (from … in … let … where … select …). "Monadic do-notation" sounds like its own terrible rollercoaster hill, but is a powerful and succinct way that lets you use a (monad) like an RX Observable in a very imperative ("Signal-like" way that is easy to read, even if the underpinnings feel hard to grasp. (LanguageExt for C# is a fascinating example in how it allows using the from/select syntax to do some interesting monadic binding, that I've seen used successfully by junior developers that don't know what a monad is, but know C# enough and find they can read/write the LINQ well enough.) I've also had some success eventually training junior developers to think in RxJS or ReactiveX in general. Marble diagrams are particularly great for visual learners to better piece together what happens. (I'm a big fan of reactivex.io's Operators pages for that, and RxJS' marble testing-based unit test harness. I've seen a similar library for C# but it didn't support all the parts of RxJS' syntax which I needed. I'd love to see that need met, too.)