feat(useObserve): accept an undefined source - #164
Merged
Conversation
The runtime already handled a factory returning `undefined`, but a source that is directly `undefined` was not part of the public overloads. This is a common case when the observable is not available yet (lazily created, from a state or a prop). `useObserve(undefined)` / `useObserve(source$ | undefined)` now typecheck and return the default value with a `complete` observable state, then start observing once an actual source is given (deps comparison recreates the store). Also allows passing `compareFn` alone (without `defaultValue`) for plain observables, which is needed for the `BehaviorSubject | undefined` case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112wLE8r3XNMqvKVSdD7Gvh
Without a source the store state is already final (`success` / `complete`), which makes `subscribe` a no-op and `source$` unreachable. We were still building a `NEVER` pipe chain (distinctUntilChanged + tap + share) and keeping an open subscription on it until unmount, for a stream that can never emit. Assign a bare `NEVER` and `Subscription.EMPTY` instead. `store.sub.unsubscribe()` in useStore stays valid (EMPTY is already closed) and nothing reads `source$` from outside the store, so the observable state is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112wLE8r3XNMqvKVSdD7Gvh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
useObservedid not accept a source that is directlyundefined, which is a common case when the observable is not available yet (lazily created, coming from a state or a prop). Consumers had to wrap it in a factory (useObserve(() => source$, [source$])) just to express "maybe not there yet".This turned out to be purely a typing gap: the store already handled a missing source (the
() => Observable<T> | undefinedfactory path), so no runtime logic changed.Changes
src/lib/binding/useObserve/useObserve.tsundefinedsource:useObserve(undefined)→data: undefined, anduseObserve(undefined, { defaultValue })→data: DefaultValue.Observable<T>toObservable<T> | undefined, so a value typedObservable<number> | undefined(orBehaviorSubject<number> | undefined) infersT = numberand yieldsdata: number | undefined.useObserve(source$ | undefined, { compareFn })— options withoutdefaultValuepreviously only existed on theBehaviorSubjectoverload, so a possibly-undefined subject had no way to pass just acompareFn.BehaviorSubject<T>(non-optional) keeps itsdata: Tguarantee; those overloads stay first in the resolution order.useStore.ts/store.tsundefined. No logic touched.Behavior when the source is undefined
The pre-existing factory semantics, unchanged:
{ data: defaultValue, status: "success", observableState: "complete" }. Because non-factory deps are[source$], the store is recreated when the source appears and observation starts then.Two points worth a maintainer decision, both left as-is here:
success/completefor "not available yet" is debatable —pending/livearguably reads better for a source you are waiting on. Kept the existing behavior rather than making a silent breaking change (the existing factory test assertssuccess/complete).useObserve(undefined)with no other arguments resolvesTtonever, sodatais exactlyundefined. Accurate, but it means the element type cannot be pre-declared on a bare undefined call. In practice consumers hit the union case (Observable<T> | undefined).Tests
Added to
useObserve.test.tsx: type assertions for the new shapes (Observable<T> | undefined,BehaviorSubject<T> | undefined, literalundefined,undefined+defaultValue) plus runtime coverage for undefined → default value, customdefaultValue, and the undefined-then-defined transition.Full suite (144 tests),
tsc, and biome all pass.Generated by Claude Code