Skip to content

docs: React patterns with @effect/atom-react + example app reference implementation - #64

Merged
fwal merged 8 commits into
mainfrom
claude/vigilant-pascal-qgjsm4
Jul 20, 2026
Merged

docs: React patterns with @effect/atom-react + example app reference implementation#64
fwal merged 8 commits into
mainfrom
claude/vigilant-pascal-qgjsm4

Conversation

@fwal

@fwal fwal commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Adds REACT.md — a guide for using effect-firebase repositories from React with @effect/atom-react — plus a reference implementation in example/app: repository atoms with a swappable Firestore layer, live queries, mutations, a validated TanStack form, and a test that swaps in @effect-firebase/mock at the registry boundary.

Includes a review pass that hardened the implementation (keepAlive on the layer atom, concurrent mutations with reactivity keys, restored error feedback on failed writes) and corrected doc claims against the shipped beta.99 APIs.

Build, tests, and lint are green across the workspace.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CvEmdZqNcYD2rEVniWLXjt


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added a React integration guide covering setup, reactive reads, mutations, forms, and testing.
    • Added a Guides section linking React and migration documentation.
    • Updated the example app with reactive post listing, creation, editing, and deletion.
    • Added loading, empty, validation, and error states for post management.
  • Tests

    • Added coverage for the empty post-list state using a mocked Firestore service.

claude added 6 commits May 22, 2026 12:00
Introduce REACT.md and reference implementation in example/app for
using effect-firebase repositories from React: a RuntimeProvider that
binds a Firestore Layer to a ManagedRuntime, plus useEffectQuery,
useEffectStream, and useEffectMutation hooks that surface a Result-
shaped state and handle fiber cleanup. Refactor the /firestore route
to use the hooks together with @tanstack/react-form and effect/Schema
(via toStandardSchemaV1) for validated CRUD. Add a Vitest setup with
@testing-library/react and a demo test that swaps in
@effect-firebase/mock at the provider boundary.

The hook surface intentionally mirrors @effect-atom/atom-react idioms
so a future migration is mechanical once atom-react supports Effect
v4. Also remove a duplicate deleteRecursive key in the mock service
that blocked the package build.

https://claude.ai/code/session_01R1D97BwwWVdGzARJY8iWeB
Replace the hand-rolled RuntimeProvider + useEffectQuery/Stream/Mutation
hooks with the upstream Effect-TS React binding now that
@effect/atom-react has shipped for Effect v4.

Repository operations live in example/app/src/lib/atoms.ts as atoms
keyed by a swappable firestoreLayerAtom: postByIdAtom, postByIdLiveAtom,
latestPostsAtom, and addPost/updatePost/deletePostAtom mutations. The
firestore route reads via useAtomValue + AsyncResult.builder and writes
via useAtomSet({ mode: 'promise' }). RegistryProvider replaces the
custom provider; tests swap the layer via initialValues. REACT.md is
rewritten to document the atom-based patterns.

https://claude.ai/code/session_01R1D97BwwWVdGzARJY8iWeB
Add a new section to REACT.md that compares the atom-based patterns
to TanStack Query side-by-side, including code excerpts for the
live-posts case, lists of where each approach wins, and conditional
recommendations based on the consumer's situation.

https://claude.ai/code/session_01R1D97BwwWVdGzARJY8iWeB
…l-qgjsm4

# Conflicts:
#	example/app/package.json
#	pnpm-lock.yaml
Fixes found by review of the React patterns work:

- Wrap firestoreLayerAtom in Atom.keepAlive. The registry GCs
  non-keepAlive atoms with no subscribers, so the seeded production
  layer was dropped moments after mount whenever the first route read
  no atoms; navigating to /firestore afterwards rebuilt the runtime
  from the default layer and every read died with a missing-service
  defect.
- Replace the unsound `Layer.empty as unknown as ...` default with a
  typed Layer.effect(FirestoreService, Effect.die(...)) that carries
  the seeding instruction, so a forgotten seed fails actionably and
  the cast no longer silences the type error.
- Pass { concurrent: true } to the mutation fn atoms. The default
  interrupts the in-flight previous invocation (latest-wins), so two
  quick deletes could drop the first write while its awaiter resolved
  with the second's result.
- Wire invalidation: reactivityKeys on mutations plus
  Atom.withReactivity on the one-shot postByIdAtom, and an idle TTL on
  postByIdLiveAtom so per-id listeners don't linger forever.
- Surface mutation failures again (lost in the atom refactor): submit
  errors are caught in onSubmit and rendered (form-core rethrows out
  of handleSubmit, so they previously became unhandled rejections with
  no UI feedback), and delete failures render an error instead of
  rejecting unobserved.
- Restore the Cancel-disabled-while-submitting guard and the
  scroll-to-form on edit from the pre-atom implementation.
- Revert tsconfig.spec.json to test-only includes with a project
  reference to tsconfig.app.json, matching the sibling packages;
  the widened include made every app source a member of two composite
  projects emitting the same declaration outputs and leaked
  vitest/globals into production code's type environment.
- Cleanups: switch AsyncResult.builder to .exhaustive(), hoist the
  Intl.DateTimeFormat and Standard Schema validator to module scope,
  drop the dead Option/Query re-export, repoEffect alias, AtomContext
  annotation, and the redundant initialValues useMemo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvEmdZqNcYD2rEVniWLXjt
- Fix three factually wrong claims verified against the shipped APIs:
  RegistryProvider reads initialValues only at registry creation (it
  never rebuilds on layer identity changes); atoms are disposed
  immediately on last unsubscribe unless keepAlive/setIdleTTL/
  defaultIdleTTL is set (no implicit idle TTL, and streams stay live
  during a TTL window rather than pausing); AsyncResult.builder only
  enforces exhaustiveness via .exhaustive() while .render() compiles
  with missing handlers and rethrows unhandled failures at runtime.
- Update the runtime-setup and repository-atom samples to match the
  hardened implementation: keepAlive + dying default layer instead of
  the unsound cast, withReactivity/reactivityKeys invalidation,
  concurrent mutations, idle TTL on the per-id live family.
- Add failure handling to the form sample and document that form-core
  rethrows onSubmit errors out of handleSubmit.
- Fix the keyed-read sample's non-compiling `id: PostId` type
  (PostId is a schema value; use `typeof PostId.Type`).
- Drop the duplicated devtools bullet from "Where it's a wash" (it is
  already listed as a Query win), and point the atom-react link at
  npm instead of the effect-smol staging repo.
- Link REACT.md and MIGRATION.md from the README so the guides are
  discoverable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvEmdZqNcYD2rEVniWLXjt
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@fwal, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f7e95646-3885-454b-92cc-33e2cc4aa556

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac0d99 and c182cca.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • example/app/package.json
📝 Walkthrough

Walkthrough

The PR adds React integration documentation and rewires the Firestore example to use registry-seeded Effect runtimes, reactive repository atoms, atom-based CRUD components, Vitest configuration, and mocked Firestore coverage.

Changes

React Firestore integration

Layer / File(s) Summary
React integration contracts and setup
REACT.md, README.md, example/app/package.json, pnpm-workspace.yaml
Documents React atom patterns and adds the required React form and atom dependencies.
Firestore runtime and repository atoms
example/app/src/lib/atoms.ts
Defines the seeded Firestore layer, derived runtime, reactive reads, live streams, and concurrent CRUD mutation atoms.
React CRUD route and runtime wiring
example/app/src/app/app.tsx, example/app/src/routes/firestore.tsx
Seeds the registry from a memoized Firebase layer and replaces local Firestore state with atom-backed form and list components.
Example test and project configuration
example/app/src/__tests__/firestore.test.tsx, example/app/tsconfig.*.json, example/app/vite.config.ts
Adds Vitest setup, TypeScript project references, and mocked Firestore coverage for the empty post-list state.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant RegistryProvider
  participant PostForm
  participant PostList
  participant PostRepository
  App->>RegistryProvider: seed memoized Firestore layer
  RegistryProvider->>PostForm: provide atom runtime
  RegistryProvider->>PostList: provide atom runtime
  PostForm->>PostRepository: add or update post
  PostList->>PostRepository: load or delete posts
  PostRepository-->>PostList: refresh reactive post state
Loading

Suggested labels: enhancement

Poem

I’m a rabbit with atoms to spare,
Wiring Firestore through React’s air.
Posts hop in, posts hop out,
Forms validate without a doubt.
Mocked streams make tests delight—
Carrots compile, and all feels right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main changes: React docs plus an example app implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The guide is about how to use the atom patterns, not why to pick them
over another library. Remove section 7 and renumber Caveats.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvEmdZqNcYD2rEVniWLXjt
@fwal fwal self-assigned this Jul 20, 2026
@fwal
fwal marked this pull request as ready for review July 20, 2026 13:17

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@example/app/package.json`:
- Around line 12-35: Move `@effect-firebase/client` and `@example/shared` from
devDependencies into dependencies alongside effect-firebase, since they are
imported at runtime by app.tsx, atoms.ts, and firestore.tsx. Remove the
duplicate vite entry from devDependencies while keeping its dependencies entry
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 79dab701-5a0c-4cc4-83ee-78bb3a944260

📥 Commits

Reviewing files that changed from the base of the PR and between f7ce213 and 1ac0d99.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • REACT.md
  • README.md
  • example/app/package.json
  • example/app/src/__tests__/firestore.test.tsx
  • example/app/src/app/app.tsx
  • example/app/src/lib/atoms.ts
  • example/app/src/routes/firestore.tsx
  • example/app/tsconfig.app.json
  • example/app/tsconfig.spec.json
  • example/app/vite.config.ts
  • pnpm-workspace.yaml

Comment thread example/app/package.json
@fwal fwal added the 📖 docs Improvements or additions to documentation label Jul 20, 2026
Move @effect-firebase/client and @example/shared to dependencies (both
are runtime imports), keep vite only in devDependencies instead of
duplicated in both blocks, and drop the peerDependencies block — the
app is private and the same packages are now real dependencies.

Addresses CodeRabbit review feedback on #64.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvEmdZqNcYD2rEVniWLXjt
@fwal
fwal merged commit 7930c3e into main Jul 20, 2026
5 checks passed
@fwal
fwal deleted the claude/vigilant-pascal-qgjsm4 branch July 20, 2026 19:07
@fwal fwal added this to the 1.0 milestone Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📖 docs Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants