Skip to content

fix: update useFirestoreDocData return type to include undefined - #733

Merged
tyler-reitz merged 2 commits into
FirebaseExtended:mainfrom
tyler-reitz:fix/observable-status-undefined-type
Jul 9, 2026
Merged

fix: update useFirestoreDocData return type to include undefined#733
tyler-reitz merged 2 commits into
FirebaseExtended:mainfrom
tyler-reitz:fix/observable-status-undefined-type

Conversation

@tyler-reitz

Copy link
Copy Markdown
Contributor

Problem

useFirestoreDocData and useFirestoreDocDataOnce were declared to return ObservableStatus<T>, but docData() from rxfire returns Observable<T | undefined> because a Firestore document may not exist. This meant callers could access data.someProperty without any type-level warning, only to get a runtime error when data was undefined.

Fix

Update the return types to ObservableStatus<T | undefined>, which accurately reflects what the observable emits. The discriminated union in ObservableStatus already handles this correctly — status: 'loading' has data: undefined, status: 'success' has data: T | undefined for these hooks.

TypeScript now correctly flags:

const { data } = useFirestoreDocData(ref);
data.someProperty; // TS18048: 'data' is possibly 'undefined'

// Narrowing works as expected:
if (status === 'success' && data) {
  data.someProperty; // OK
}

Fixes #577

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates type definitions across several files. It replaces JSX.Element with React.ReactElement in auth.tsx, performance.tsx, and storage.tsx, and adds an explicit React.ReactElement return type in sdk.tsx. Additionally, it updates the return types of useFirestoreDocData and useFirestoreDocDataOnce in firestore.tsx to allow undefined values (ObservableStatus<T | undefined>). There are no review comments, and I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

docData() from rxfire returns Observable<T | undefined> because a
Firestore document may not exist. The previous ObservableStatus<T>
return type was a lie — callers would get undefined at runtime with
no type-level warning.

Fixes FirebaseExtended#577
@tyler-reitz
tyler-reitz force-pushed the fix/observable-status-undefined-type branch from 11e645d to 2bdb63a Compare July 9, 2026 21:33
@tyler-reitz
tyler-reitz merged commit 38c8ab7 into FirebaseExtended:main Jul 9, 2026
12 checks passed
@tyler-reitz
tyler-reitz deleted the fix/observable-status-undefined-type branch July 9, 2026 21:41
tyler-reitz added a commit to tyler-reitz/reactfire that referenced this pull request Jul 21, 2026
…ebaseExtended#733)

* fix: update useFirestoreDocData return type to include undefined

docData() from rxfire returns Observable<T | undefined> because a
Firestore document may not exist. The previous ObservableStatus<T>
return type was a lie — callers would get undefined at runtime with
no type-level warning.

Fixes FirebaseExtended#577

* docs: regenerate API reference after ObservableStatus type fix
tyler-reitz added a commit to tyler-reitz/reactfire that referenced this pull request Jul 28, 2026
Exploratory work toward the half of FirebaseExtended#749 the gate currently hands to a
human: is a .d.ts change additive or breaking. Nothing here runs in CI.
Committed so the design work survives, and so the findings are attached
to the code that produced them.

Approach: let tsc adjudicate assignability rather than hand-rolling
subtyping. For every symbol exported by both versions, emit probe lines
asserting assignability in each direction, compile, read the errors.
About 3.4s for the whole package.

Correct on every real version pair, including concluding unprompted that
4.2.6 is still breaking relative to 4.2.3 and that the reason is exactly
the two hooks FirebaseExtended#733 changed. 4.2.3 -> 4.2.4 reduces 39 raw failures to one
root cause, ObservableStatus.

Four findings worth keeping, each caught by a control rather than by
reading the code:

  - Classes with private members are nominally typed, so two copies are
    never assignable and 4.2.6 compared as breaking against itself.
    Comparing a version to itself is the cheapest available control.
  - Root attribution has to walk non-exported types, or nine symbols look
    like independent roots while their declarations are byte-identical.
  - Only new->old failing means a consumer break. The other direction
    alone means the API got more permissive.
  - Generic constraints cannot be synthesised away, and instantiating
    with any/unknown hides the very break this exists to catch.

Also settles a question that was open: this composes with the gate's
textual .d.ts diff rather than replacing it. Adding an optional property
is invisible to assignability but is still a semver minor, so the diff
answers "did anything change" and this answers "is it breaking".

Known gaps are listed in the README: the private-member stripping is a
line regex rather than an AST walk, and local types are keyed by bare
name so cross-file collisions can mis-attribute a root.
tyler-reitz added a commit to tyler-reitz/reactfire that referenced this pull request Jul 29, 2026
Exploratory work toward the half of FirebaseExtended#749 the gate currently hands to a
human: is a .d.ts change additive or breaking. Nothing here runs in CI.
Committed so the design work survives, and so the findings are attached
to the code that produced them.

Approach: let tsc adjudicate assignability rather than hand-rolling
subtyping. For every symbol exported by both versions, emit probe lines
asserting assignability in each direction, compile, read the errors.
About 3.4s for the whole package.

Correct on every real version pair, including concluding unprompted that
4.2.6 is still breaking relative to 4.2.3 and that the reason is exactly
the two hooks FirebaseExtended#733 changed. 4.2.3 -> 4.2.4 reduces 39 raw failures to one
root cause, ObservableStatus.

Four findings worth keeping, each caught by a control rather than by
reading the code:

  - Classes with private members are nominally typed, so two copies are
    never assignable and 4.2.6 compared as breaking against itself.
    Comparing a version to itself is the cheapest available control.
  - Root attribution has to walk non-exported types, or nine symbols look
    like independent roots while their declarations are byte-identical.
  - Only new->old failing means a consumer break. The other direction
    alone means the API got more permissive.
  - Generic constraints cannot be synthesised away, and instantiating
    with any/unknown hides the very break this exists to catch.

Also settles a question that was open: this composes with the gate's
textual .d.ts diff rather than replacing it. Adding an optional property
is invisible to assignability but is still a semver minor, so the diff
answers "did anything change" and this answers "is it breaking".

Known gaps are listed in the README: the private-member stripping is a
line regex rather than an AST walk, and local types are keyed by bare
name so cross-file collisions can mis-attribute a root.
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

Successfully merging this pull request may close these issues.

Type of ObservableStatus::data should include undefined

2 participants