Skip to content

feat(rivetkit/react): add suspense support#4485

Open
jog1t wants to merge 1 commit intomainfrom
03-24-feat_rivetkit_react_add_suspense_support
Open

feat(rivetkit/react): add suspense support#4485
jog1t wants to merge 1 commit intomainfrom
03-24-feat_rivetkit_react_add_suspense_support

Conversation

@jog1t
Copy link
Contributor

@jog1t jog1t commented Mar 23, 2026

Description

This pull request adds React Suspense support to the @rivetkit/react package, allowing components to suspend while actor connections are being established. The implementation includes a new suspense option for the useActor hook that integrates with React's <Suspense> boundary for declarative loading states and <ErrorBoundary> for error handling.

Key changes include:

  • Added suspense option to ActorOptions and ActorStateReference interfaces
  • Enhanced the actor cache to track connection promises for Suspense integration
  • Modified the connection creation logic to resolve/reject promises based on connection status
  • Added comprehensive test coverage for both regular and Suspense modes
  • Updated documentation with Suspense usage examples

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

The changes have been tested with a comprehensive test suite that covers:

  • Regular mode tests: Verifying existing behavior remains unchanged, including connection states, error handling, and event subscriptions
  • Suspense mode tests: Testing suspension during connection, error boundary integration, render count optimization, and proper connection state after resolution
  • Edge cases: Testing disabled connections, re-enabling connections, and proper cleanup

Tests use mock clients and connections to simulate various connection scenarios and verify the correct behavior in both synchronous and asynchronous contexts.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@railway-app
Copy link

railway-app bot commented Mar 23, 2026

🚅 Deployed to the rivet-pr-4485 environment in rivet-frontend

Service Status Web Updated (UTC)
frontend-cloud 😴 Sleeping (View Logs) Web Mar 23, 2026 at 11:43 pm
website 😴 Sleeping (View Logs) Web Mar 23, 2026 at 11:40 pm
frontend-inspector 😴 Sleeping (View Logs) Web Mar 23, 2026 at 11:37 pm
kitchen-sink ❌ Build Failed (View Logs) Web Mar 23, 2026 at 11:32 pm
mcp-hub ✅ Success (View Logs) Web Mar 23, 2026 at 11:30 pm
ladle ❌ Build Failed (View Logs) Web Mar 23, 2026 at 11:29 pm

Copy link
Contributor Author

jog1t commented Mar 23, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@jog1t jog1t marked this pull request as ready for review March 23, 2026 23:29
@claude
Copy link

claude bot commented Mar 23, 2026

Code Review: feat(rivetkit/react): add suspense support

Overall this is a well-thought-out implementation with a solid test suite. A few issues worth addressing before merge.

Bug: use() requires React 19, but peer deps still allow React 18

mod.ts imports use from react, but use() was introduced in React 19. The package.json peer deps say "react": "^18 || ^19", so React 18 users who set suspense: true will get a runtime error (or a build error since use won't be in the React 18 types). Either drop React 18 from peer deps, or add a runtime guard and document the React 19 requirement in the README.

Potential double-create() call via queueMicrotask

When suspense is enabled and the actor is cached+idle, a microtask is queued on both the cache-hit and cache-miss paths. React's strict mode double-invokes renders, so two microtasks could be queued before either fires. The connStatus === "idle" guard inside each microtask means at most one will actually call create(), so this is safe. A comment explaining why the double-queue is harmless would help future readers.

getConnectPromise() silently returns Promise.resolve() if cache entry is missing

If getConnectPromise() is called before the cache entry exists, it returns a resolved promise. use(Promise.resolve()) causes the component to render without suspending, potentially while connStatus is still "idle" or "connecting", which breaks the suspense guarantee. This shouldn't happen in normal usage, but it would be silent if it did. Consider throwing an Error here to make the broken state visible.

Comment style (CLAUDE.md)

Per the project's style guide, comments should be complete sentences. The comment // connectPromise intentionally kept (rejected) so use() rethrows should be rewritten as a complete sentence, e.g. // Keep the rejected promise so use() rethrows it on the next render.

Removed JSDoc comments

mod.ts removes the JSDoc from useActor and useEvent. These are public API surface and inline JSDoc drives IDE hover docs. The README is a good addition but doesn't replace hover documentation. Consider keeping at least a summary line for useActor.

Minor: conditional use() may trigger lint warnings

use() is explicitly designed to be called conditionally (unlike hooks), so this is correct. A short comment noting this pre-empts future "conditional hook" lint errors from tools that don't distinguish use() from regular hooks.

Tests look solid

The test suite covers the key scenarios well: initial connecting state, error exposure, enabled: false, re-enable, useEvent, fallback during suspend, error boundary on connection failure, render count bounds, and connection non-null after resolve. Good coverage.

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.

1 participant