[Question] Why useMemo and useRef instead of useEffect?
rassie opened this issue · 7 comments
I'm sorry for hijacking issues for a question, but I've struggled to find an explanation on my own and I think that this might be useful for other people.
On more than one occasion, Google search produces a common pattern for subscribing to observables in React context:
React.useEffect(() => {
const sub = observable$.subscribe();
return () => sub.unsubscribe();
}, [])
In most cases, this works fine, but sometimes, it fails spectacularly. For example, I've encountered a situation in which
React.useEffect(() => {
const sub = combineLatest(o1$, o2$, o3$, o4$).subscribe();
return () => sub.unsubscribe();
}, [])
it getting initialized, but somehow combineLatest
fails to subscribe to the observables o1$
through o4$
properly, while doing combineLatest(o1$, o2$, o3$, o4$).subscribe()
just outside the useEffect
hook works as expected. I've tried creating a minimal test case for this situation, but failed, since most of the times, that useEffect
pattern does work and I have not been able to figure out which parts of my observables make it fail.
Now, the epiphany: I've tried replacing my useEffect
s with useSubscription
from observable-hooks
and sure enough, my problematic code started working again. After I've looked into useSubscription
s code, I've noticed that useEffect
is only used for unsubscription, subscription is handled via useMemo
and useRef
.
Are there any reasons why you implemented useSubscription
this way? Is there any explanation why this combination works better that useEffect
?
The reason of choosing useMemo
is so that synchronous values from observables can be used as initial state without triggering an extra re-rendering.
But do note that this is safe for now but not safe in concurrent mode. I've been working on a concurrent mode compatible branch.
Back to your issue. It is probably one or more of your observables are hot and start emitting values before the component reaches commit phase where the subscription is performed.
I am also working on an api for this issue. Like returning an indicator showing the ready state of subscription.
Any comment is wellcome!
Can you test your code with useLayoutEffect
and see if it works? I am trying to make this into the new version.
Thank you for the explanation! Happy to report that useSubscription
provides exactly the behaviour I was expecting, useLayoutEffect
has been about as effective as useEffect
, i.e. not at all. Still not sure why my observables did not subscribe properly -- even though they are hot, I've been using shareReplay(1)
, so there should have been at least one initial next
on the combined observable. Either way, observable-hooks
has been the saviour for now, let's hope concurrent mode does not break a whole lot... :)
share*
operators need at least one subscription to activate.
So more of a "warm" observable? But either way, combineLatest
is supposed to provide those subscriptions, isn't it?
not really. combineLatest
won't do anything until you subscribe
.
Are you able to control the emission timing of the hot observables?
Just to make sure workaround exists for this type of issues before finishing the new version.