feat: add loaded flag to React hooks
Summary
Add a loaded boolean property to all React hook results to distinguish between:
-
Not loaded yet:
loaded=false, data is in initial/empty state -
Loaded:
loaded=true, data reflects actual query results (may be empty)
This solves a fundamental UX problem where hooks initially return empty data structures before the first fetch completes, making it impossible to distinguish between "loading" and "legitimately empty results".
Changes
Core Implementation
- Created base
HookResultinterface withreadonly loaded: boolean - Updated all hook result types to extend
HookResult:-
LiveQueryResult -
AllDocsResult -
ChangesResult -
UseDocumentResultObject
-
Hook Implementations
-
useLiveQuery: Tracks loaded state, sets to
trueafter firstrefreshRows()completes -
useDocument: Tracks loaded state, sets to
trueafter firstrefresh()completes -
useAllDocs: Tracks loaded state, sets to
trueafter firstrefreshRows()completes -
useChanges: Tracks loaded state, sets to
trueafter firstrefreshRows()completes
Testing
- Added comprehensive test suite for
loadedbehavior - Tests validate loaded flag correctly distinguishes empty vs not-yet-loaded states
- All existing tests continue to pass
Usage
const { rows, docs, loaded } = useLiveQuery("type", { key: "todo" });
const { doc, loaded, save } = useDocument({ title: "" });
const { docs, loaded } = useAllDocs();
const { docs, loaded } = useChanges([], {});
if (!loaded) {
return <LoadingSpinner />;
}
// Now we know we have real data (or confirmed empty results)
return <TodoList items={docs} />;
Test Plan
- [x] All existing tests pass
- [x] New tests verify loaded flag starts false
- [x] New tests verify loaded flag becomes true after data loads
- [x] Test validates empty results with loaded=true are distinguishable from not-yet-loaded
- [x] Tests cover all four hooks: useLiveQuery, useDocument, useAllDocs, useChanges
- [x] TypeScript diagnostics are clean
Breaking Changes
None - this is a purely additive change. The loaded property is added to existing return types.
π€ Generated with Claude Code
Summary by CodeRabbit
-
New Features
- Added a readonly
loadedflag to hook results so apps can distinguish "not yet loaded" vs "loaded (including empty)" across live queries, document, all-docs, and changes hooks;loadedresets when query/params/document change.
- Added a readonly
-
Tests
- Added test suites validating loaded-flag behavior: initial false, transition to true, empty-result handling, reset-on-change scenarios, and proper teardown.
The lifecycle is per query / hook, not per db, so this interface makes sense.
[!NOTE]
Other AI code review bot(s) detected
CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.
Walkthrough
Hooks (useLiveQuery, useDocument, useAllDocs, useChanges) and related types now expose a readonly hydrated flag; hooks use per-request IDs to ignore stale async responses and reset hydrated when inputs change. Types updated; tests added/adjusted to validate hydration lifecycle and resets.
Changes
| Cohort / File(s) | Summary |
|---|---|
Type definitions use-fireproof/react/types.ts |
Add interface HookResult { readonly hydrated: boolean }. Make LiveQueryResult an intersection with HookResult. Make AllDocsResult, ChangesResult, and UseDocumentResultObject extend HookResult. Change UseFireproof.attach public signature from AttachState to () => AttachState. |
Hooks β hydration + request gating use-fireproof/react/use-live-query.ts, use-fireproof/react/use-document.ts, use-fireproof/react/use-all-docs.ts, use-fireproof/react/use-changes.ts |
Add local hydrated state (init false), per-request requestId refs, reset hydrated when inputs change, gate async refresh results by request id to ignore stale responses, set hydrated = true after successful refresh, and return { ...result, hydrated }. |
Tests β hydration semantics & resets use-fireproof/tests/use-fireproof.test.tsx, use-fireproof/tests/use-fireproof-loaded-resets.test.tsx, use-fireproof/tests/use-all-docs.test.tsx, use-fireproof/tests/* |
Add suites and assertions for hydrated lifecycle and reset-on-dependency-change. Switch many tests to beforeAll/afterAll, use unique DB names, seed once, and reduce timeouts to 5000ms. |
Other β logging / mocks / timeouts core/svc/api/database-api.ts, core/tests/svc/full-round-trip.test.ts, use-fireproof/tests/img-file.test.tsx, use-fireproof/tests/* |
Add a console shim suppressing console.log (preserve warn/error); replace verbose mocks with no-op-resolving mocks; reduce various test timeouts to 5000ms. |
Cloud setup cleanup cloud/backend/cf-d1/setup-backend-d1.ts, cloud/backend/cf-d1/setup.cloud.d1.ts |
Adjust readiness detection string and add a readiness log; remove large commented-out setup blocks leaving only preset env call. |
Websocket cleanup semantics cloud/backend/base/msg-dispatcher-impl.ts |
Close matching WebSocket with specific code and reason (close(4711, "closed by /close-connection")) when resetting connection. |
Sequence Diagram(s)
sequenceDiagram
participant Component
participant Hook as Hook (useLiveQuery / useDocument / useAllDocs / useChanges)
participant Data as DataSource
Component->>Hook: mount / call hook
activate Hook
Hook-->>Component: initial render (hydrated = false)
Component->>Hook: effect triggers refresh
Hook->>Data: fetch request (tagged with requestId)
activate Data
Data-->>Hook: response (rows/docs)
deactivate Data
alt response matches latest requestId
Note right of Hook `#ccf5d0`: apply results\nset hydrated = true
Hook-->>Component: re-render with hydrated = true
else stale response
Note right of Hook `#fef3c7`: ignore stale response
end
deactivate Hook
Estimated code review effort
π― 4 (Complex) | β±οΈ ~45 minutes
Files/areas to inspect closely:
-
use-fireproof/react/types.tsβ exported type changes andUseFireproof.attachsignature. -
use-fireproof/react/use-document.tsβ initialDoc id-change handling, request-id gating, and no-id edge cases. - Consistency of requestId increment/comparison and reset logic across all hooks.
- New/modified tests β timing, flakiness risk, and correctness of hydration assertions.
Possibly related PRs
- fireproof-storage/fireproof#702 β Overlaps changes to useLiveQuery internals and returned result shape.
- fireproof-storage/fireproof#705 β Overlaps hook refresh/state-update logic for live queries and all-docs.
- fireproof-storage/fireproof#801 β Related to the attach/attach-state refactor and changes to attach signature.
Suggested reviewers
- necrodome
Pre-merge checks and finishing touches
β Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | β Passed | Check skipped - CodeRabbitβs high-level summary is enabled. |
| Title check | β Passed | The title 'feat: add loaded flag to React hooks' accurately describes the main change: introducing a 'loaded' flag to React hooks. It is specific, concise, and clearly summarizes the primary objective of the PR. |
| Docstring Coverage | β Passed | Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%. |
β¨ Finishing touches
- [ ] π Generate docstrings
π§ͺ Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
- [ ] Commit unit tests in branch
jchris/loaded-hooks
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.