Skip to content

docs: warn about react_component helper collision with react-rails (#3143)#3160

Merged
justin808 merged 10 commits intomainfrom
jg/3143-react-rails-helper-collision-docs
Apr 23, 2026
Merged

docs: warn about react_component helper collision with react-rails (#3143)#3160
justin808 merged 10 commits intomainfrom
jg/3143-react-rails-helper-collision-docs

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Apr 18, 2026

Summary

  • Addresses Document or fix helper collision when react_on_rails and react-rails coexist #3143: incremental migrations from react-rails break silently as soon as react_on_rails is added because both gems define a react_component view helper with incompatible signatures, and Rails helper auto-loading lets the second-defined one win. Un-migrated views then raise ArgumentError at render time.
  • Expands docs/oss/migrating/migrating-from-react-rails.md with a preflight callout and a new Coexistence: keeping both gems installed during a staged migration section covering:
    • why the collision happens (both helpers, signature diff, Rails helper load order),
    • a one-liner rg / grep to audit legacy positional calls before adding the gem,
    • Option A — migrate all call sites in the same PR (recommended),
    • Option B — preserve legacy react-rails semantics via a prepended LegacyReactComponent shim and expose an explicit react_on_rails_component(...) alias for migrated mounts (the pattern the hackclub/hcb migration actually used),
    • known limitations of Option B (app-level only, temporary, remove when done).
  • Docs-only change. No CHANGELOG entry per .claude/docs/changelog-guidelines.md (docs fixes are excluded).

Why docs, not a code change

The issue proposes either (1) avoid taking over the default helper when react-rails is detected, or (2) document the collision. Option 1 would mean changing the semantics of a public view helper based on runtime gem detection — brittle and a backwards-compat risk for everyone who already migrated. The reporter ended up using exactly the two shims now documented in Option B, so codifying that path is the pragmatic fix and leaves room for a future product-level coexistence mode if demand warrants it.

Test plan

  • npx prettier --check docs/oss/migrating/migrating-from-react-rails.md passes.
  • Pre-commit hooks (trailing newlines, lychee offline link check, prettier) pass.
  • Pre-push lychee online link check passes — all 6 links resolve.
  • Internal anchor #legacy-compatibility-fixes-that-often-make-migration-one-shot matches an existing heading in the same file.
  • Reviewer sanity-checks the Option B shim snippet against a toy react-rails + react_on_rails app (not verified in this PR — the pattern is lifted from the reporter's working migration).

Fixes #3143.

🤖 Generated with Claude Code


Note

Low Risk
Low risk: documentation-only changes. Main risk is misleading guidance for coexistence shims if copied verbatim, but no runtime code is changed in this repo.

Overview
Adds a new preflight warning and an expanded coexistence section in migrating-from-react-rails.md explaining that react-rails and react_on_rails both define react_component with incompatible signatures, which can cause runtime ArgumentError or silent prop loss during staged migrations.

Provides quick audit commands to find legacy call sites and documents two mitigation paths: migrate all mounts to the options-hash form in the same PR, or temporarily add an app-level initializer shim that preserves legacy react_component behavior and introduces an explicit react_on_rails_component alias (with limitations and cleanup guidance).

Reviewed by Cursor Bugbot for commit 5394237. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Documentation
    • Added guidance for staged migrations where two React integration libraries coexist, warning about a view-helper signature collision that can cause runtime errors or silent prop loss. Includes commands to locate affected call sites and two mitigation paths: update mounts to the newer options-style form, or apply a temporary app-level shim/alias during migration (with listed limitations and restart requirements).

Incremental migrations from react-rails were breaking silently as soon as
react_on_rails was added: both gems define react_component with
incompatible signatures, and Rails helper auto-loading makes the
second-defined one win. Un-migrated views then raise ArgumentError.

Extend the migration guide with a preflight callout and a new
"Coexistence" section that explains the collision, shows how to detect
affected call sites, and documents two paths:
- migrate all call sites in one PR (recommended), or
- preserve legacy react-rails semantics via a prepended shim and use an
  explicit react_on_rails_component alias for migrated mounts.

Addresses #3143.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a “Coexistence” section to the React Rails migration guide documenting that react-rails and react_on_rails both define react_component, how Rails helper resolution causes react_on_rails to override the legacy helper, concrete runtime effects for legacy positional calls, grep-based detection commands, and two mitigation paths (convert or shim).

Changes

Cohort / File(s) Summary
Migration Documentation
docs/oss/migrating/migrating-from-react-rails.md
Adds a “Coexistence” section describing the react_component helper name/signature collision when react-rails and react_on_rails coexist; explains Rails engine load-order/helper precedence; documents runtime outcomes for legacy positional calls (3+ positional args → ArgumentError; exactly 2 positional args → props dropped); provides grep audit commands to find affected call sites; outlines two mitigation options: (A) migrate calls to the options-hash form in a single PR, or (B) add an app-level to_prepare initializer shim that preserves legacy react_component while introducing a react_on_rails_component alias, and lists the shim’s limitations (migration-only lifecycle, initializer-restart requirement, app-level scope, explicit aliasing for server rendering/Pro features).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 I nibble through helpers at the break of dawn,

Where old calls linger and new gems have drawn.
Convert a few, or stitch a gentle seam —
I guard lost props and shepherd the dream. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding documentation about the react_component helper collision warning between react_on_rails and react-rails.
Linked Issues check ✅ Passed The PR fully addresses issue #3143 by documenting the helper collision, explaining both failure modes (ArgumentError with 3+ args, silent prop loss with 2 args), providing audit commands, and outlining two migration mitigation paths.
Out of Scope Changes check ✅ Passed The PR is a documentation-only change focused entirely on the react_component helper collision issue; no unrelated code modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg/3143-react-rails-helper-collision-docs

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 and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9f78c1339

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
docs/oss/migrating/migrating-from-react-rails.md (1)

169-169: Optional: consider "impractical" for conciseness.

Minor style suggestion: "If a single-PR migration is impractical" reads slightly more concise than "is not practical."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/oss/migrating/migrating-from-react-rails.md` at line 169, Replace the
phrase "is not practical" with the more concise "is impractical" in the sentence
that mentions keeping react-rails' react_component semantics and introducing a
separate helper for migrated mounts; locate the line containing the string "If a
single-PR migration is not practical, you can keep `react-rails`'s
`react_component` semantics..." and update it to read "If a single-PR migration
is impractical, you can keep `react-rails`'s `react_component` semantics..." to
apply the style suggestion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/oss/migrating/migrating-from-react-rails.md`:
- Line 169: Replace the phrase "is not practical" with the more concise "is
impractical" in the sentence that mentions keeping react-rails' react_component
semantics and introducing a separate helper for migrated mounts; locate the line
containing the string "If a single-PR migration is not practical, you can keep
`react-rails`'s `react_component` semantics..." and update it to read "If a
single-PR migration is impractical, you can keep `react-rails`'s
`react_component` semantics..." to apply the style suggestion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee864d01-fb5a-4998-874f-0c973080d51b

📥 Commits

Reviewing files that changed from the base of the PR and between e9e27a2 and b9f78c1.

📒 Files selected for processing (1)
  • docs/oss/migrating/migrating-from-react-rails.md

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 18, 2026

Code Review — docs: warn about react_component helper collision with react-rails (#3143)

Overall: Good docs addition that addresses a real, reproducible migration footgun. The explanation of why the collision happens is accurate, and Option A/B structure is clear. A few issues worth addressing before merge.


Issues

1. Monkey-patch lines belong in an initializer, not a helper file (medium)

The shim file is placed in app/helpers/react_on_rails_coexistence.rb, but the two lines at the bottom:

```ruby
ReactOnRailsHelper.prepend(ReactOnRailsCoexistence::LegacyReactComponent)
ActionView::Base.include(ReactOnRailsCoexistence)
```

…are global monkey-patches that must run before any view renders. In development, Zeitwerk loads app/helpers/ files lazily — a file is only autoloaded when its constant is first referenced. If ReactOnRailsCoexistence is never referenced before the first request (e.g. no eager load), these patches don't fire, and the first un-migrated view still raises ArgumentError.

Rails' helper :all typically triggers the autoload in time, but it's fragile to rely on. The conventional and reliable place for global module surgery is config/initializers/. I'd recommend splitting:

  • Keep the module definition in app/helpers/react_on_rails_coexistence.rb
  • Move the two prepend/include lines to config/initializers/react_on_rails_coexistence.rb with a note that it must load after both gems' engines initialize

2. "silently" is misleading (minor)

This happens silently on any view that has not been migrated yet.

The failure is not silent — it raises ArgumentError on the first render of any un-migrated view. What's silent is the boot-time behavior: there's no warning logged at startup. Suggest: "Rails gives no boot-time warning; the first request to any un-migrated view will raise ArgumentError at render time."

3. ReactOnRails::Helper vs ReactOnRailsHelper should be explained (minor)

The shim references both ReactOnRailsHelper (for prepend) and ReactOnRails::Helper (for instance_method). These are different objects — ReactOnRailsHelper is the thin wrapper in app/helpers/react_on_rails_helper.rb that does include ReactOnRails::Helper. A reader who hasn't read the gem source may be confused by the asymmetry.

The reason using ReactOnRails::Helper.instance_method(:react_component) in react_on_rails_component is intentional and correct: it bypasses the prepended LegacyReactComponent override and reaches the original RoR implementation directly. That subtlety is worth a one-line comment in the snippet.

4. Known limitations could mention mailers (minor)

The shim is app-level; it will not help gem-provided engines or view partials that make legacy react_component calls.

Action Mailer views that use react_component would also be unaffected by the shim (mailers have separate view contexts). Worth adding alongside engines.


What's correct

  • The technical description of the collision mechanism is accurate: ReactOnRailsHelper includes ReactOnRails::Helper, the latter defines react_component, and whichever gem loads last wins the method lookup.
  • React::Rails::ViewHelper.instance_method(:react_component).bind(self) is valid Ruby because the ActionView context has React::Rails::ViewHelper in its ancestor chain (react-rails' engine ensures this).
  • The ReactOnRails::Helper.instance_method(:react_component).bind(self) call in react_on_rails_component correctly bypasses the prepended override — the prepend is on ReactOnRailsHelper, not ReactOnRails::Helper, so fetching the unbound method from ReactOnRails::Helper directly avoids infinite recursion.
  • The anchor link #legacy-compatibility-fixes-that-often-make-migration-one-shot is correct.
  • Option A recommendation (migrate in one PR) is the right default advice.

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 18, 2026

Greptile Summary

This docs-only PR adds a preflight warning and a new "Coexistence" section to migrating-from-react-rails.md, documenting the silent react_component helper collision that occurs when both react-rails and react_on_rails are installed simultaneously, along with two migration strategies (full same-PR migration or a temporary shim).

Confidence Score: 5/5

Safe to merge; docs-only change with one minor P2 suggestion about a superfluous require line in the example initializer.

All findings are P2 (style/best-practice). The shim logic, ancestor-chain explanation, and to_prepare pattern are technically correct. The single remaining note — removing a require that conflicts with Zeitwerk autoloading — does not block a documentation merge and does not affect any runtime code.

Line 207 of docs/oss/migrating/migrating-from-react-rails.md — the example initializer has a superfluous require that should be removed for Rails 7+/Zeitwerk compatibility.

Important Files Changed

Filename Overview
docs/oss/migrating/migrating-from-react-rails.md Adds preflight item and full coexistence section for react-rails + react_on_rails staged migration; shim logic is sound but the example initializer includes a superfluous require that conflicts with Zeitwerk autoloading in Rails 7+.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Add react_on_rails to Gemfile] --> B{Legacy react_component calls in views?}
    B -- No --> C[Add gem, migrate freely]
    B -- Yes --> D{Can migrate all call sites in one PR?}
    D -- Yes --> E[Option A: Convert all calls to options-hash form in same PR]
    E --> F[No collision — only react_on_rails helper active]
    D -- No --> G[Option B: Shim approach]
    G --> H[Create app/helpers/react_on_rails_coexistence.rb]
    H --> I[Create config/initializers/react_on_rails_coexistence.rb]
    I --> J[to_prepare: prepend LegacyReactComponent to ReactOnRailsHelper]
    J --> K[to_prepare: include ReactOnRailsCoexistence into ActionView::Base]
    K --> L{View calls react_component?}
    L -- legacy call --> M[LegacyReactComponent delegates to React::Rails::ViewHelper]
    L -- migrated call --> N[use react_on_rails_component alias]
    N --> O[Calls ReactOnRails::Helper#react_component directly]
    M --> P[Incremental migration — remove shim when done]
    O --> P
Loading

Reviews (2): Last reviewed commit: "docs: address PR review feedback on reac..." | Re-trigger Greptile

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
- Clarify that the helper collision is driven by engine initializer
  load order, not file order in app/helpers/
- Forward &block in the LegacyReactComponent shim so react-rails
  block-form calls keep working (react-rails' react_component accepts
  a block for mount-tag content)
- Split Option B into a helper file + initializer; apply the
  prepend/include inside Rails.application.config.to_prepare so
  patches run once at boot and are reapplied after dev reloads
  (avoiding Zeitwerk autoload timing issues and duplicate ancestors)
- Reword "happens silently" to clarify that ArgumentError is raised
  at render time; Rails gives no boot-time warning
- Add a comment in the react_on_rails_component snippet explaining
  why the unbound method is fetched from ReactOnRails::Helper (not
  ReactOnRailsHelper) to avoid infinite recursion
- Add Action Mailer and Rails engines to the Option B known
  limitations alongside gem-provided engines

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review feedback addressed in edc08c2

All seven inline review comments on this PR have been addressed and the threads resolved.

MUST-FIX (3)

  • Helper collision explanation (@greptile-apps[bot], line 143) — reworded to make clear the collision is driven by each gem's engine initializer load order, not file order under app/helpers/, so readers don't chase file-ordering as a fix.
  • Block forwarding in the shim (@chatgpt-codex-connector[bot], lines 182–185) — verified against react-rails' lib/react/rails/view_helper.rb that react_component(*args, &block) does accept a block. The shim now captures &block on the signature and forwards it via .call(name, props, options, &block).
  • Move prepend/include to an initializer (@claude[bot], @greptile-apps[bot]) — Option B is now split into two files: the module body stays in app/helpers/react_on_rails_coexistence.rb, and the prepend/include calls live in config/initializers/react_on_rails_coexistence.rb inside Rails.application.config.to_prepare so they run deterministically at boot and are reapplied after every Zeitwerk reload (avoiding both the autoload-timing fragility and the ancestor-chain accumulation in dev).

OPTIONAL (3)

  • "Silently" wording (@claude[bot], line 149) — replaced with the suggested wording making clear ArgumentError is raised at render time; Rails gives no boot-time warning.
  • Comment explaining ReactOnRails::Helper vs ReactOnRailsHelper asymmetry (@claude[bot], line 191) — added an inline comment in the react_on_rails_component snippet noting that fetching the unbound method from ReactOnRails::Helper directly bypasses the prepended LegacyReactComponent override and avoids infinite recursion.
  • Action Mailer in Known Limitations (@claude[bot], line 213) — the bullet now lists Rails engines and Action Mailer views alongside gem-provided engines.

Rationale for summary comments not replied-to individually

  • @coderabbitai's walkthrough comment is an auto-generated PR overview with no actionable items of its own.
  • @claude[bot]'s full review summary issue comment enumerates the same four findings that are already tracked in the inline threads — addressing the inline comments covers it in full.
  • @greptile-apps[bot]'s "Greptile Summary" issue comment enumerates the same two P2 findings already tracked in its inline threads — addressing the inline comments covers it in full.

Thanks to everyone for the reviews.

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

@justin808 — thanks for the thorough summary! The fixes all look well-reasoned:

  • The to_prepare-wrapped initializer split is the right call — it ensures the ancestor-chain mutation runs after Zeitwerk reloads in development and only once at boot in production, avoiding the accumulation problem.
  • Forwarding &block in the shim signature is a correctness fix that would have been a subtle breakage for any caller passing a block to react_component.
  • Tightening the collision explanation to point at engine initializer load order (rather than file order under app/helpers/) prevents readers from going down the wrong rabbit hole.

I'll trigger a re-review to verify the updated commit.


🧠 Learnings used
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-21T08:53:17.309Z
Learning: For small, focused PRs (roughly 5 files or fewer with one clear purpose): use at most one AI reviewer for inline comments, batch review fixes into one follow-up push, and verify language/runtime/library claims locally before making code changes in response to AI review

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-12T23:15:11.806Z
Learning: Applies to CHANGELOG.md : Changelog format: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` (no hash before PR number)

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 3094
File: react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb:40-64
Timestamp: 2026-04-13T02:28:35.099Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb`, the `typeof generateRSCPayload !== 'function'` guards in `rsc_payload_function_section` are an intentional faithful port of the existing pattern in `server_rendering_js_code.rb` (lines 21 and 39). Do not flag this guard as a bug in this file; any fix should be tracked as a separate issue covering both files.

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-12T23:15:11.806Z
Learning: For small, focused PRs (5 files changed or fewer with one clear purpose): use at most one AI reviewer that leaves inline comments; additional AI tools should be summary-only or used manually

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2674
File: react_on_rails/lib/react_on_rails/doctor.rb:2325-2345
Timestamp: 2026-03-19T16:13:07.375Z
Learning: In `react_on_rails/lib/react_on_rails/doctor.rb`, `check_rsc_payload_route` and `check_rsc_procfile_watcher` filter commented lines (`/^\s*#/`) before checking for the target string, consistent with the `config_has_async_loading_strategy?` pattern. The doctor avoids a full Rails boot for resilience and uses file-content scanning instead of actual Rails route inspection. Remaining accepted limitations are dynamic/engine-mounted routes and routes loaded via the `draw` macro (uncommon, since the generator places `rsc_payload_route` directly in `config/routes.rb`).

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2790
File: docs/oss/migrating/rsc-data-fetching.md:238-238
Timestamp: 2026-03-22T03:17:13.316Z
Learning: In `docs/oss/migrating/rsc-data-fetching.md` (React on Rails migration guide), the phrase "Rails fetches the data in the controller layer" is intentional and acceptable wording even when the ERB examples show inline ActiveRecord queries inside `stream_react_component`/`stream_react_component_with_async_props`. The inline queries are a teaching shortcut; "controller layer" is meant to convey that data fetching happens on the Rails side (not in React), not to mandate that queries must live in a controller action. Do not flag this phrasing as misleading in future reviews.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 0
File: :0-0
Timestamp: 2026-04-13T02:29:20.920Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/engine.rb` and `react_on_rails/lib/react_on_rails/configuration.rb` (PR `#3094`): Do NOT reset `rendering_strategy` or `js_code_builder` inside `ReactOnRails.configure`. The Pro engine sets these exactly once during `after_initialize`; resetting them on reconfigure would silently drop Pro's NodeStrategy/ProJsCodeBuilder assignments. A proper reconfigure mechanism for these accessors is deferred to Step 3 of the strategy-pattern refactor (`#2905`).

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2773
File: react_on_rails/lib/react_on_rails/doctor.rb:1262-1265
Timestamp: 2026-03-22T23:36:07.592Z
Learning: In React on Rails, the RSC generator always installs `stream_react_component` in `app/views/`. Therefore, the view-file scan in `uses_prerender_in_views?` (in `react_on_rails/lib/react_on_rails/doctor.rb`) reliably detects RSC-enabled apps via `stream_react_component`, and there is no need to additionally scan `config/routes.rb` for `rsc_payload_route` to cover the route-driven RSC entry point.

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/dummy/commonWebpackConfig.js : Changes to `commonWebpackConfig.js` affect all three webpack bundles (client, server, and RSC) in the Pro dummy app

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2651
File: docs/oss/introduction.md:47-47
Timestamp: 2026-03-26T02:20:12.940Z
Learning: In `docs/oss/introduction.md` (and related docs in the `shakacode/react_on_rails` repo), the link to `docs/oss/getting-started/comparison-with-alternatives.md` described as "a side-by-side matrix with benchmarks" is intentionally and accurately worded. The page includes actual benchmark sections (e.g., Vite vs Rspack benchmark tables), so the phrase "with benchmarks" should NOT be flagged as misleading in future reviews.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2857
File: README.md:0-0
Timestamp: 2026-03-26T08:32:31.881Z
Learning: In `README.md` (and related docs) for `shakacode/react_on_rails`, the Ruby minimum version (`>= 3.0`) similarly reflects the actual package validator/gemspec floor, while `CI tested: 3.2 - 3.4` describes coverage. This distinction is intentional and should not be flagged as a docs/CI mismatch.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2850
File: docs/oss/api-reference/rails-view-rendering-from-inline-javascript.md:0-0
Timestamp: 2026-03-26T07:01:43.653Z
Learning: In `packages/react-on-rails/src/ClientRenderer.ts`, the duplicate-render guard (which skips rendering if the DOM node is already connected/mounted) applies only to the automatic page-load rendering path. The public `ReactOnRails.render(...)` API routes through `packages/react-on-rails/src/base/client.ts` and calls `reactHydrateOrRender(...)` directly, without any deduplication guard. Therefore, repeated calls to the public `ReactOnRails.render(...)` API on the same DOM node (e.g., `#root`) WILL invoke React again — they are NOT deduplicated. Documentation stating this behavior is correct and should not be flagged.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2789
File: docs/pro/streaming-ssr.md:79-81
Timestamp: 2026-03-21T23:48:01.271Z
Learning: In React on Rails Pro, `stream_react_component` (defined in `react_on_rails_pro_helper.rb` around lines 127-129) forces `prerender: true` internally. The option is not configurable by callers, so documentation examples should NOT include `prerender: true` in `stream_react_component` invocations as it is misleading.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2610
File: docs/pro/react-server-components/create-without-ssr.md:13-22
Timestamp: 2026-03-24T21:38:26.825Z
Learning: In `docs/pro/react-server-components/create-without-ssr.md`, JS install instructions for React on Rails Pro should only install `react-on-rails-pro` (not `react-on-rails` separately), because the Pro package already includes the base package functionality. Use `--exact` (yarn/pnpm) or `--save-exact` (npm) flags to pin to an exact version for reproducibility.

Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2886
File: docs/sidebars.ts:9-251
Timestamp: 2026-03-29T08:38:50.670Z
Learning: In the `shakacode/react_on_rails` repository, `docs/sidebars.ts` uses sidebar doc IDs **without** an `oss/` prefix (e.g., `'api-reference/javascript-api'`, `'building-features/caching'`). Although OSS doc files are physically located at `docs/oss/...`, Docusaurus is configured to remap that path so those IDs resolve correctly without the prefix. Pro docs use the `pro/` prefix (e.g., `'pro/react-on-rails-pro'`). Do not suggest adding an `oss/` prefix to sidebar IDs in this repo.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: edc08c2699

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Clarify that 2-argument legacy calls like `react_component "Foo", props`
do not raise — they are silently accepted and drop props — while only
3+ argument calls raise ArgumentError. The previous wording implied
every un-migrated view would fail loudly, which could mislead readers
doing staged migrations into missing silent prop loss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review feedback addressed in 054c6ea

Addressed

  • Silent prop loss for two-argument legacy calls (@chatgpt-codex-connector[bot], line 149) — the prior wording said every un-migrated view would raise ArgumentError, which is only true for 3+ positional arguments. Verified against react_on_rails/lib/react_on_rails/helper.rb:84 that the signature is react_component(component_name, options = {}), so a 2-arg legacy call is accepted silently and drops props. The "Why it collides" section now splits the two failure modes explicitly — 3+ args raise ArgumentError, 2 args silently lose props — and the "Detecting the collision quickly" section calls out two-arg calls as a silent-failure risk that deserves extra scrutiny during staged migrations. Thread resolved.

Not addressed (rationale)

  • "is not practical" → "is impractical" (@coderabbitai[bot], review nitpick on line 169) — declining this. The two phrasings are style-equivalent; "is not practical" reads slightly softer and fits the tone of the surrounding migration guide. Not worth the doc churn.

Thanks.

Comment thread docs/oss/migrating/migrating-from-react-rails.md
Comment thread docs/oss/migrating/migrating-from-react-rails.md
@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Tip

For best results, initiate chat on the files or code changes.

@justin808 — the silent prop-loss correction is genuinely the more important fix: the 3-arg ArgumentError is noisy and caught quickly in testing, but the 2-arg silent prop drop is the kind of bug that survives to production and is hard to trace back to a helper collision. Splitting the two failure modes in both the "Why it collides" and "Detecting" sections makes the migration guide much more actionable.

Happy to accept the "is not practical" call — the phrasing is fine as-is.


✏️ Learnings added
Learnt from: justin808
URL: https://github.com/shakacode/react_on_rails/pull/3160

Timestamp: 2026-04-18T08:02:37.259Z
Learning: In `react_on_rails/lib/react_on_rails/helper.rb` (line 84), the `react_component` helper signature is `react_component(component_name, options = {})`. When react_on_rails wins the helper-name collision over react-rails, there are two distinct failure modes for legacy positional-style calls: (1) calls with 3+ positional arguments raise `ArgumentError` immediately at render time; (2) calls with exactly 2 positional arguments (component_name + props hash) are accepted silently but the props are dropped/ignored, causing a silent data-loss bug that is harder to detect. Migration docs should distinguish both failure modes explicitly.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2790
File: docs/oss/migrating/rsc-data-fetching.md:238-238
Timestamp: 2026-03-22T03:17:13.316Z
Learning: In `docs/oss/migrating/rsc-data-fetching.md` (React on Rails migration guide), the phrase "Rails fetches the data in the controller layer" is intentional and acceptable wording even when the ERB examples show inline ActiveRecord queries inside `stream_react_component`/`stream_react_component_with_async_props`. The inline queries are a teaching shortcut; "controller layer" is meant to convey that data fetching happens on the Rails side (not in React), not to mandate that queries must live in a controller action. Do not flag this phrasing as misleading in future reviews.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 3094
File: react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb:40-64
Timestamp: 2026-04-13T02:28:35.099Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb`, the `typeof generateRSCPayload !== 'function'` guards in `rsc_payload_function_section` are an intentional faithful port of the existing pattern in `server_rendering_js_code.rb` (lines 21 and 39). Do not flag this guard as a bug in this file; any fix should be tracked as a separate issue covering both files.

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2674
File: react_on_rails/lib/react_on_rails/doctor.rb:2325-2345
Timestamp: 2026-03-19T16:13:07.375Z
Learning: In `react_on_rails/lib/react_on_rails/doctor.rb`, `check_rsc_payload_route` and `check_rsc_procfile_watcher` filter commented lines (`/^\s*#/`) before checking for the target string, consistent with the `config_has_async_loading_strategy?` pattern. The doctor avoids a full Rails boot for resilience and uses file-content scanning instead of actual Rails route inspection. Remaining accepted limitations are dynamic/engine-mounted routes and routes loaded via the `draw` macro (uncommon, since the generator places `rsc_payload_route` directly in `config/routes.rb`).

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 0
File: :0-0
Timestamp: 2026-04-13T02:29:20.920Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/engine.rb` and `react_on_rails/lib/react_on_rails/configuration.rb` (PR `#3094`): Do NOT reset `rendering_strategy` or `js_code_builder` inside `ReactOnRails.configure`. The Pro engine sets these exactly once during `after_initialize`; resetting them on reconfigure would silently drop Pro's NodeStrategy/ProJsCodeBuilder assignments. A proper reconfigure mechanism for these accessors is deferred to Step 3 of the strategy-pattern refactor (`#2905`).

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2651
File: docs/oss/introduction.md:47-47
Timestamp: 2026-03-26T02:20:12.940Z
Learning: In `docs/oss/introduction.md` (and related docs in the `shakacode/react_on_rails` repo), the link to `docs/oss/getting-started/comparison-with-alternatives.md` described as "a side-by-side matrix with benchmarks" is intentionally and accurately worded. The page includes actual benchmark sections (e.g., Vite vs Rspack benchmark tables), so the phrase "with benchmarks" should NOT be flagged as misleading in future reviews.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2850
File: docs/oss/api-reference/rails-view-rendering-from-inline-javascript.md:0-0
Timestamp: 2026-03-26T07:01:43.653Z
Learning: In `packages/react-on-rails/src/ClientRenderer.ts`, the duplicate-render guard (which skips rendering if the DOM node is already connected/mounted) applies only to the automatic page-load rendering path. The public `ReactOnRails.render(...)` API routes through `packages/react-on-rails/src/base/client.ts` and calls `reactHydrateOrRender(...)` directly, without any deduplication guard. Therefore, repeated calls to the public `ReactOnRails.render(...)` API on the same DOM node (e.g., `#root`) WILL invoke React again — they are NOT deduplicated. Documentation stating this behavior is correct and should not be flagged.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2497
File: react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:0-0
Timestamp: 2026-04-09T06:56:01.856Z
Learning: In `react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb`, config overrides for the error scenarios hub are NOT applied by mutating the ReactOnRails/ReactOnRailsPro global config singletons. Instead, the private helper `error_hub_config_value(key, default)` reads overrides directly from `params` and falls back to the live singleton value — no global state is mutated during request handling. Do not flag the absence of an `around_action` restore pattern here, as the design intentionally avoids mutation altogether.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2857
File: README.md:0-0
Timestamp: 2026-03-26T08:32:31.881Z
Learning: In `README.md` (and related docs) for `shakacode/react_on_rails`, the Ruby minimum version (`>= 3.0`) similarly reflects the actual package validator/gemspec floor, while `CI tested: 3.2 - 3.4` describes coverage. This distinction is intentional and should not be flagged as a docs/CI mismatch.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2789
File: docs/pro/streaming-ssr.md:79-81
Timestamp: 2026-03-21T23:48:01.271Z
Learning: In React on Rails Pro, `stream_react_component` (defined in `react_on_rails_pro_helper.rb` around lines 127-129) forces `prerender: true` internally. The option is not configurable by callers, so documentation examples should NOT include `prerender: true` in `stream_react_component` invocations as it is misleading.

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2773
File: react_on_rails/lib/react_on_rails/doctor.rb:1262-1265
Timestamp: 2026-03-22T23:36:07.592Z
Learning: In React on Rails, the RSC generator always installs `stream_react_component` in `app/views/`. Therefore, the view-file scan in `uses_prerender_in_views?` (in `react_on_rails/lib/react_on_rails/doctor.rb`) reliably detects RSC-enabled apps via `stream_react_component`, and there is no need to additionally scan `config/routes.rb` for `rsc_payload_route` to cover the route-driven RSC entry point.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2610
File: docs/pro/react-server-components/create-without-ssr.md:13-22
Timestamp: 2026-03-24T21:38:26.825Z
Learning: In `docs/pro/react-server-components/create-without-ssr.md`, JS install instructions for React on Rails Pro should only install `react-on-rails-pro` (not `react-on-rails` separately), because the Pro package already includes the base package functionality. Use `--exact` (yarn/pnpm) or `--save-exact` (npm) flags to pin to an exact version for reproducibility.

Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2886
File: docs/sidebars.ts:9-251
Timestamp: 2026-03-29T08:38:50.670Z
Learning: In the `shakacode/react_on_rails` repository, `docs/sidebars.ts` uses sidebar doc IDs **without** an `oss/` prefix (e.g., `'api-reference/javascript-api'`, `'building-features/caching'`). Although OSS doc files are physically located at `docs/oss/...`, Docusaurus is configured to remap that path so those IDs resolve correctly without the prefix. Pro docs use the `pro/` prefix (e.g., `'pro/react-on-rails-pro'`). Do not suggest adding an `oss/` prefix to sidebar IDs in this repo.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 18, 2026

Review: docs — react_component helper collision

Docs-only change, no runtime risk. Overall this is a well-researched and genuinely useful addition. The coexistence scenario is real and the Option B shim is technically sound Ruby. A few issues worth addressing before merging.


Strengths

  • The root-cause explanation (ancestor chain, engine initializer order) is accurate and will save teams hours of head-scratching.
  • The audit rg / grep one-liner is the right first step to recommend before adding the gem.
  • The shim code is technically correct:
    • Prepending LegacyReactComponent to ReactOnRailsHelper correctly intercepts the default react_component name.
    • Fetching ReactOnRails::Helper.instance_method(:react_component) (not ReactOnRailsHelper.instance_method) correctly bypasses the prepend to avoid infinite recursion.
    • bind(self) is valid here because self (an ActionView instance) has both helper modules in its ancestor chain.
    • Placing the prepend/include calls inside to_prepare is the right pattern for surviving development reloads.

Issues

1. Incomplete failure-mode description (line 149) — moderate

The section correctly calls out that 3-arg calls raise ArgumentError, but two-argument calls silently misbehave instead of raising:

<%= react_component "MyWidget", @props %>

Under ReactOnRails::Helper#react_component(name, options = {}), @props is accepted as options with no error. The component renders but receives no props because options[:props] is nil. This silent regression is arguably more dangerous than a loud ArgumentError.
(Inline suggestion posted on line 149.)

2. "Rename back" wording is misleading (line 221) — minor

"rename react_on_rails_component back to react_component" implies there's a method to rename; there isn't. The actual teardown is: delete the two shim files and update call sites back to react_component.
(Inline suggestion posted on line 221.)

3. Load-order framing implies false precision (line 143) — minor

"Whichever engine initializer runs last … wins" is accurate but doesn't tell the reader which gem wins in a typical setup. In practice react_on_rails almost always wins (later alphabetically, typically added later to Gemfile), but this is not guaranteed. Worth one sentence noting that teams shouldn't rely on ordering to resolve the collision.
(Inline note posted on line 143.)

4. React::Rails::ViewHelper guard — very minor

If the file app/helpers/react_on_rails_coexistence.rb is ever loaded in an environment where react-rails is absent (e.g. a future cleanup step that removes the gem but forgets the initializer), React::Rails::ViewHelper.instance_method(...) raises NameError at boot. A one-line guard (return unless defined?(React::Rails::ViewHelper)) inside the to_prepare block would make the teardown process more forgiving, though it's easy to omit given the migration context.


Verdict

Issues 2–4 are minor polish. Issue 1 (silent 2-arg failure) is the most meaningful gap since it describes a class of breakage that's harder to detect than ArgumentError. Once that and the phrasing nits are addressed this is good to merge.

Address remaining PR review feedback on the react-rails coexistence
section:

- Replace the inaccurate "rename react_on_rails_component back to
  react_component" cleanup step with the actual procedure (update each
  call site and delete the two shim files).
- Note that react_on_rails wins the helper collision in practice because
  of initializer sort order, but that teams should not rely on ordering
  to resolve the conflict.
- Tighten "is not practical" to "is impractical".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review feedback addressed in 5149483

All remaining review threads on this PR are now resolved.

Addressed

  • Cleanup wording was misleading (@claude[bot], line 221) — the step "delete the shim and rename react_on_rails_component back to react_component" implied a method rename. Reworded to match the actual procedure: update each migrated call site from react_on_rails_component(...) back to react_component(...) and delete both shim files (app/helpers/react_on_rails_coexistence.rb and config/initializers/react_on_rails_coexistence.rb).
  • "Second-loaded wins" framing too confident (@claude[bot], line 143) — added a sentence noting that in practice react_on_rails wins in almost all setups because its engine initializer sorts later alphabetically and is typically added to the Gemfile after react-rails, while cautioning that this is an artifact of sort order and should not be relied on as a resolution strategy.
  • "Is not practical" → "is impractical" (@coderabbitai[bot], line 169, review summary) — applied the style tightening.

Already addressed in 054c6ea

  • Silent prop loss for two-argument legacy calls (@claude[bot], line 149) — functionally the same feedback as the resolved codex-connector thread. The docs at lines 143–146 already split the two failure modes explicitly: 3+ positional arguments raise ArgumentError, 2 positional arguments are silently accepted with props bound to options (the "more dangerous case"). Posted a rationale reply and resolved the thread.

All changes are documentation-only.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Tip

For best results, initiate chat on the files or code changes.

@justin808 — all three fixes are solid:

  • The cleanup procedure reword is a real accuracy improvement — "rename the method" vs. "update each call site and delete the shim files" are very different operations and the old wording would have confused readers at exactly the moment they're trying to safely exit the coexistence phase.
  • The "second-loaded wins" caveat strikes the right balance: it gives readers enough context to understand why react_on_rails wins in practice, while explicitly flagging that sort order is an artifact and not a stable contract to engineer against.
  • Glad the "is impractical" tightening made it in.

The migration guide is in good shape — the two failure-mode split, the to_prepare-wrapped initializer, the &block forwarding, and the load-order explanation all make this a much more production-safe reference than what existed before.


🧠 Learnings used
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 0
File: :0-0
Timestamp: 2026-04-18T08:02:37.259Z
Learning: In `react_on_rails/lib/react_on_rails/helper.rb` (line 84), the `react_component` helper signature is `react_component(component_name, options = {})`. When react_on_rails wins the helper-name collision over react-rails, there are two distinct failure modes for legacy positional-style calls: (1) calls with 3+ positional arguments raise `ArgumentError` immediately at render time; (2) calls with exactly 2 positional arguments (component_name + props hash) are accepted silently but the props are dropped/ignored, causing a silent data-loss bug that is harder to detect. Migration docs should distinguish both failure modes explicitly.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2790
File: docs/oss/migrating/rsc-data-fetching.md:238-238
Timestamp: 2026-03-22T03:17:13.316Z
Learning: In `docs/oss/migrating/rsc-data-fetching.md` (React on Rails migration guide), the phrase "Rails fetches the data in the controller layer" is intentional and acceptable wording even when the ERB examples show inline ActiveRecord queries inside `stream_react_component`/`stream_react_component_with_async_props`. The inline queries are a teaching shortcut; "controller layer" is meant to convey that data fetching happens on the Rails side (not in React), not to mandate that queries must live in a controller action. Do not flag this phrasing as misleading in future reviews.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2850
File: docs/oss/api-reference/rails-view-rendering-from-inline-javascript.md:0-0
Timestamp: 2026-03-26T07:01:43.653Z
Learning: In `packages/react-on-rails/src/ClientRenderer.ts`, the duplicate-render guard (which skips rendering if the DOM node is already connected/mounted) applies only to the automatic page-load rendering path. The public `ReactOnRails.render(...)` API routes through `packages/react-on-rails/src/base/client.ts` and calls `reactHydrateOrRender(...)` directly, without any deduplication guard. Therefore, repeated calls to the public `ReactOnRails.render(...)` API on the same DOM node (e.g., `#root`) WILL invoke React again — they are NOT deduplicated. Documentation stating this behavior is correct and should not be flagged.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 0
File: :0-0
Timestamp: 2026-04-13T02:29:20.920Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/engine.rb` and `react_on_rails/lib/react_on_rails/configuration.rb` (PR `#3094`): Do NOT reset `rendering_strategy` or `js_code_builder` inside `ReactOnRails.configure`. The Pro engine sets these exactly once during `after_initialize`; resetting them on reconfigure would silently drop Pro's NodeStrategy/ProJsCodeBuilder assignments. A proper reconfigure mechanism for these accessors is deferred to Step 3 of the strategy-pattern refactor (`#2905`).

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 3094
File: react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb:40-64
Timestamp: 2026-04-13T02:28:35.099Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb`, the `typeof generateRSCPayload !== 'function'` guards in `rsc_payload_function_section` are an intentional faithful port of the existing pattern in `server_rendering_js_code.rb` (lines 21 and 39). Do not flag this guard as a bug in this file; any fix should be tracked as a separate issue covering both files.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2497
File: react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:0-0
Timestamp: 2026-04-09T06:56:01.856Z
Learning: In `react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb`, config overrides for the error scenarios hub are NOT applied by mutating the ReactOnRails/ReactOnRailsPro global config singletons. Instead, the private helper `error_hub_config_value(key, default)` reads overrides directly from `params` and falls back to the live singleton value — no global state is mutated during request handling. Do not flag the absence of an `around_action` restore pattern here, as the design intentionally avoids mutation altogether.

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2674
File: react_on_rails/lib/react_on_rails/doctor.rb:2325-2345
Timestamp: 2026-03-19T16:13:07.375Z
Learning: In `react_on_rails/lib/react_on_rails/doctor.rb`, `check_rsc_payload_route` and `check_rsc_procfile_watcher` filter commented lines (`/^\s*#/`) before checking for the target string, consistent with the `config_has_async_loading_strategy?` pattern. The doctor avoids a full Rails boot for resilience and uses file-content scanning instead of actual Rails route inspection. Remaining accepted limitations are dynamic/engine-mounted routes and routes loaded via the `draw` macro (uncommon, since the generator places `rsc_payload_route` directly in `config/routes.rb`).

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2651
File: docs/oss/introduction.md:47-47
Timestamp: 2026-03-26T02:20:12.940Z
Learning: In `docs/oss/introduction.md` (and related docs in the `shakacode/react_on_rails` repo), the link to `docs/oss/getting-started/comparison-with-alternatives.md` described as "a side-by-side matrix with benchmarks" is intentionally and accurately worded. The page includes actual benchmark sections (e.g., Vite vs Rspack benchmark tables), so the phrase "with benchmarks" should NOT be flagged as misleading in future reviews.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2857
File: README.md:0-0
Timestamp: 2026-03-26T08:32:31.881Z
Learning: In `README.md` (and related docs) for `shakacode/react_on_rails`, the Ruby minimum version (`>= 3.0`) similarly reflects the actual package validator/gemspec floor, while `CI tested: 3.2 - 3.4` describes coverage. This distinction is intentional and should not be flagged as a docs/CI mismatch.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2789
File: docs/pro/streaming-ssr.md:79-81
Timestamp: 2026-03-21T23:48:01.271Z
Learning: In React on Rails Pro, `stream_react_component` (defined in `react_on_rails_pro_helper.rb` around lines 127-129) forces `prerender: true` internally. The option is not configurable by callers, so documentation examples should NOT include `prerender: true` in `stream_react_component` invocations as it is misleading.

Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2773
File: react_on_rails/lib/react_on_rails/doctor.rb:1262-1265
Timestamp: 2026-03-22T23:36:07.592Z
Learning: In React on Rails, the RSC generator always installs `stream_react_component` in `app/views/`. Therefore, the view-file scan in `uses_prerender_in_views?` (in `react_on_rails/lib/react_on_rails/doctor.rb`) reliably detects RSC-enabled apps via `stream_react_component`, and there is no need to additionally scan `config/routes.rb` for `rsc_payload_route` to cover the route-driven RSC entry point.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2610
File: docs/pro/react-server-components/create-without-ssr.md:13-22
Timestamp: 2026-03-24T21:38:26.825Z
Learning: In `docs/pro/react-server-components/create-without-ssr.md`, JS install instructions for React on Rails Pro should only install `react-on-rails-pro` (not `react-on-rails` separately), because the Pro package already includes the base package functionality. Use `--exact` (yarn/pnpm) or `--save-exact` (npm) flags to pin to an exact version for reproducibility.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2886
File: docs/sidebars.ts:9-251
Timestamp: 2026-03-29T08:38:50.670Z
Learning: In the `shakacode/react_on_rails` repository, `docs/sidebars.ts` uses sidebar doc IDs **without** an `oss/` prefix (e.g., `'api-reference/javascript-api'`, `'building-features/caching'`). Although OSS doc files are physically located at `docs/oss/...`, Docusaurus is configured to remap that path so those IDs resolve correctly without the prefix. Pro docs use the `pro/` prefix (e.g., `'pro/react-on-rails-pro'`). Do not suggest adding an `oss/` prefix to sidebar IDs in this repo.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
…tion

- Split the long "Why it collides" paragraph into two so the failure-mode
  bullets open their own paragraph (review #3106012646).
- Document bind(self)'s precondition (self must include React::Rails::ViewHelper)
  and mention bind_call as a Ruby 2.7+ alternative (review #3106012927).
- Note in the to_prepare block that Ruby skips duplicate prepend/include so
  reloads don't accumulate ancestors (review #3106013062).
- Add an explicit "two-rename" callout so readers expect call sites to flip
  name twice across the migration lifecycle (review #3105700862).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review round-up

I (Claude Code) addressed the seven open review threads on the coexistence section. Three were already covered by db2273b4b; the remaining four doc-polish items are now in fdcd2923e.

Already addressed in db2273b

  • Alphabetical-sort wording (thread) — the paragraph was reworked to frame the collision as a helper-precedence issue (include_all_helpers pulling RoR's helper into the controller helper module that sits earlier than ActionView::Base in method lookup) and to explicitly call out that it's "not app/helpers/ file order or gem-name alphabetics."
  • require blocks hot-reload (thread) — Known Limitations now states a server restart is required after editing the shim.
  • "Infinite recursion" comment misleading (thread) — the react_on_rails_component comment now says it bypasses the prepended override regardless of include order, instead of citing recursion.

Fixed in fdcd292

  • Two-rename note (thread) — added an explicit Note callout under Option B explaining that each migrated call site flips name twice (react_componentreact_on_rails_component while the shim is active, then back).
  • Paragraph split (thread) — the "Why it collides" paragraph now ends after "…before relying on coexistence," and the failure-mode bullets open in a fresh paragraph beginning "Once you add react_on_rails to the Gemfile…".
  • bind(self) precondition (thread) — the LegacyReactComponent snippet now documents that bind(self) requires self to include React::Rails::ViewHelper (true in standard Rails views via the railtie, but not in Rails engines, ViewComponent, or other non-ActionView::Base contexts), and mentions bind_call as the Ruby 2.7+ alternative.
  • to_prepare reload safety (thread) — added a comment in the to_prepare block noting that Ruby skips the insertion when the module is already in the ancestor chain, so reloads don't accumulate ancestors.

All seven threads have been resolved. CodeRabbit's most recent review reported "No actionable comments" — no further changes from that bot.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 19, 2026

Review: docs — react_component helper collision during staged migration

Overview

Solid, well-structured addition. The preflight warning (item 7 in the checklist) and the silent-failure description (two-argument form silently drops props) are the most valuable parts — that failure mode is genuinely non-obvious. The explanation of Rails helper-precedence mechanics is accurate.

A few things worth addressing before merge:


Issues

1. bind_call mentioned in comment but not used

The shim's inline comment says "On Ruby 2.7+, .bind_call(self, ...) skips the BoundMethod allocation" but then calls .bind(self).call(...). React on Rails already requires Ruby 2.7+, so if bind_call is preferable it should be used; otherwise the hint is misleading. The same pattern appears in react_on_rails_component. Either switch both to bind_call or drop the comment.

2. Shim file in app/helpers/ loaded via bare require

Putting the module in app/helpers/react_on_rails_coexistence.rb means Zeitwerk registers ReactOnRailsCoexistence as an autoload constant for that path. The initializer's require races Zeitwerk to define it, which usually wins in practice but is fragile — in eager-load mode (production) Zeitwerk's eager loader may reopen or conflict with the constant. A cleaner home is lib/react_on_rails_coexistence.rb (outside Zeitwerk's autoload paths), or just define the module inline in the initializer since it's migration-only scaffolding anyway.

3. Detection command only scans app/views

Apps using ViewComponent, Phlex, or Cells keep React mounts outside app/views. A note to also check app/components (or omit the path to scan the whole project) would make the audit more complete.

4. Untested shim (acknowledged in PR description)

Worth surfacing this caveat in the docs themselves with a brief callout: "This pattern is adapted from a real migration; test it in your specific app before relying on it in production."


Strengths

  • The two-argument silent-failure case is called out clearly and early — that is the genuinely dangerous scenario.
  • config.to_prepare for the patch setup is correct; using after_initialize or a bare initializer would not survive development reloads.
  • Known limitations section is honest and complete.
  • Preflight item 7 with a direct anchor link is exactly the right UX for a checklist.

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Address review feedback on the react_component collision section:

- Define the ReactOnRailsCoexistence module directly in the initializer
  instead of splitting across app/helpers/ and config/initializers/. This
  keeps the shim out of Zeitwerk's autoload paths and avoids bookkeeping
  confusion in production eager-load mode.
- Switch both helpers from .bind(self).call(...) to .bind_call(self, ...)
  so the code matches the Ruby 2.7+ guidance mentioned in the comment.
  React on Rails requires Ruby 3.0+, so bind_call is always available.
- Broaden the detection grep to include app/components and note that
  users with Phlex/mailer/engine mounts should expand the path further
  or drop it entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review round-up

I (Claude Code) addressed the four open review threads from the 2026-04-19 10:02 review — all resolved in acd6b3257.

What changed

  1. bind_call consistency in LegacyReactComponent#react_component (@claude[bot]) — the comment claimed bind_call was better on Ruby 2.7+ but the code used .bind(self).call(...). Switched to .bind_call(self, name, props, options, &block) and removed the stale "consider bind_call" comment since the gem requires Ruby 3.0+.
  2. Same inconsistency in react_on_rails_component (@claude[bot]) — also switched to .bind_call(self, name, options).
  3. Zeitwerk autoload concern for app/helpers/react_on_rails_coexistence.rb (@claude[bot]) — inlined the module definition directly in config/initializers/react_on_rails_coexistence.rb and removed the separate app/helpers/ file. Initializers aren't on Zeitwerk's autoload paths, so the require vs. autoload race is gone. Added a one-line rationale in the doc so readers know why the module lives in the initializer.
  4. Grep path too narrow (@claude[bot]) — expanded to app/views app/components and added a sentence pointing users at Phlex views, mailer templates, and engine mounts if those apply, plus the option to drop the path entirely.

All four threads have been resolved.

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
Comment thread docs/oss/migrating/migrating-from-react-rails.md
Comment thread docs/oss/migrating/migrating-from-react-rails.md
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 23, 2026

Review

Overall: High-quality docs addition that addresses a real, under-documented pain point. The technical analysis of why the collision happens is accurate (confirmed against the actual helper module structure: ReactOnRailsHelper simply includes ReactOnRails::Helper, and the shim targets the correct modules). The detection commands are practical, and the limitations section is appropriately cautious. A few issues worth addressing before merge.


What works well

  • The explanation of Rails helper module precedence (vs. load order or gem alphabetics) is technically accurate and will save readers a lot of confusion.
  • The two-failure-mode breakdown (3+ positional args → ArgumentError; 2 positional args → silent prop loss) is exactly the right mental model. The silent-loss case being flagged as more dangerous is a good call.
  • The to_prepare block is correctly written: include/prepend idempotency in Ruby means repeated reloads in development won't accumulate duplicate ancestors.
  • Placing the shim in config/initializers/ (not app/helpers/) to avoid Zeitwerk autoload conflicts is good advice.

Issues

1. Wording inconsistency in preflight item 7 (inline, line 15)
"the second-loaded one silently wins" implies gem order in Gemfile controls the outcome, but the body correctly explains it's helper module precedence. A reader who skims only the preflight list may chase the wrong fix.

2. bind_call requires Ruby 2.7+ (inline, lines 190, 201)
Both LegacyReactComponent and react_on_rails_component use UnboundMethod#bind_call, which was added in Ruby 2.7. Ruby 2.6 is EOL so this is minor, but a one-line note (or a bind(self).call(...) fallback) prevents a cryptic NoMethodError for anyone still on it.

3. Shim code is unverified (inline, lines 205–210)
The PR's own test plan marks the shim as not tested against an actual two-gem app. The docs present it without any caveat, which implies it's officially recommended. Either add a visible "community-sourced, verify in staging" note, or add a minimal smoke test before merging.

4. Option B double-rename friction is understated (inline, line 221)
The Note at the bottom mentions the two rename passes but only briefly. Teams choosing Option B specifically because a single-PR migration is impractical may not realize they're committing to two project-wide rename sweeps — which can exceed the cost of the original Option A migration. This deserves a more prominent callout in the "Known limitations" list.


Minor nits (non-blocking)

  • The grep command in "Detecting the collision quickly" searches only app/views app/components, but the paragraph above it mentions Phlex views, mailer templates, and gem engines. The example path list could match the prose, e.g. app/views app/components app/mailers (with a note to add engines separately).
  • "Rails gives no boot-time warning in either case" is true today but may change if Rails adds a helper-conflict warning in a future version. Phrasing like "as of Rails 7/8" would make this future-proof.

- Fix preflight item 7 wording: "second-loaded" implied gem/load order,
  but body correctly explains it's Rails helper module precedence.
- Add Ruby 2.7+ caveat with `bind(self).call(...)` fallback for the
  `UnboundMethod#bind_call` calls in the shim.
- Add a callout marking the shim as community-sourced and not covered
  by the react_on_rails test suite; recommend staging verification.
- Foreground the two project-wide rename passes as the first bullet in
  Known limitations of Option B, removing the now-duplicate trailing Note.
- Include `app/mailers` in the detection grep example so it matches the
  surrounding prose.
- Qualify "Rails gives no boot-time warning" with "As of Rails 7/8" to
  future-proof the claim.
@justin808
Copy link
Copy Markdown
Member Author

Review round-up

I (Claude Code) addressed the four open review threads and both summary-review nits from the 2026-04-23 review — all resolved in 172fce356.

Addressed (inline threads)

  • Preflight item 7 wording (@claude[bot], line 15) — "the second-loaded one silently wins" was inconsistent with the body, which explains helper module precedence determines the winner regardless of gem load order. Applied the reviewer's suggested rewording verbatim so skim-readers of the preflight can't conclude gem reordering is the fix.
  • Ruby 2.7+ caveat for bind_call (@claude[bot], line 190) — Added a > **Requires Ruby 2.7+** callout right after the code block with the .bind(self).call(...) fallback for Ruby 2.6. Covers both bind_call uses in the shim.
  • Shim is unverified (@claude[bot], lines 205–210) — Added a callout above the code block marking the initializer as community-sourced, not covered by the react_on_rails test suite, and recommending staging verification before production use.
  • Option B double-rename friction (@claude[bot], line 221) — Foregrounded the two project-wide rename passes as the first bullet of "Known limitations of Option B", explicitly noting the cost can equal or exceed Option A on large apps. Removed the now-duplicate trailing Note and kept the find-and-replace tip inline.

Addressed (summary-review minor nits)

  • Detection grep example didn't match the prose — Added app/mailers to both the rg and grep -rEn examples. The surrounding prose already directs readers to add app/ paths for Phlex and engines; the example now matches the most common case the prose calls out.
  • "Rails gives no boot-time warning in either case" may age poorly — Qualified with "As of Rails 7/8" so the claim is explicitly scoped to current Rails versions.

All four inline threads are resolved.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 23, 2026

Good docs addition addressing a real silent-failure footgun. Structure and A/B option split are clear. A few issues in the Option B shim to address before merge.

Comment thread docs/oss/migrating/migrating-from-react-rails.md
Comment thread docs/oss/migrating/migrating-from-react-rails.md
Comment thread docs/oss/migrating/migrating-from-react-rails.md
- Detection commands: added `app/helpers` to the `rg`/`grep` path list so
  view-helper wrappers that call `react_component` from Ruby are not missed.
- Shim inline comment: spelled out that `bind_call` raises `TypeError` at
  render time when `self` doesn't include `React::Rails::ViewHelper`, and
  pointed readers at the Known Limitations section.
- Known Limitations: upgraded the "will not help" bullet to make explicit
  that unsupported view contexts (Rails engines, ViewComponent, Action
  Mailer, gem engines) hard-fail with `TypeError` rather than degrade
  gracefully, and added a new bullet on cleanup ordering: delete
  `config/initializers/react_on_rails_coexistence.rb` in the same commit
  that drops `react-rails` from the `Gemfile`, otherwise any request that
  still routes through the legacy delegate raises `NameError: uninitialized
  constant React::Rails::ViewHelper` at render time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review round-up

I (Claude Code) addressed the three open review threads from the 2026-04-23 02:21 review — all resolved in 2e272eca4.

Addressed

  • Detection command path list (thread, @claude[bot], line 155) — added app/helpers to the rg/grep path list so view-helper wrappers that call react_component from Ruby aren't missed, and added a one-line note explaining why.
  • bind_call TypeError note in the shim comment (thread, @claude[bot], line 191) — the existing inline comment explained the self requirement but not the failure mode. Reworded it to make explicit that bind_call raises TypeError at render time when self doesn't include React::Rails::ViewHelper, and pointed at the Known Limitations section.
  • Known Limitations — crash semantics and cleanup ordering (thread, @claude[bot], near line 232) — upgraded the "will not help" bullet to spell out the TypeError hard-fail in unsupported view contexts (Rails engines, ViewComponent, Action Mailer, gem engines), and added a new bullet on cleanup ordering: delete config/initializers/react_on_rails_coexistence.rb in the same commit that drops react-rails from the Gemfile, otherwise any request still routing through the legacy delegate raises NameError: uninitialized constant React::Rails::ViewHelper at render time. I kept the failure point described as render time (not boot) because the constant is referenced lazily inside the shim's method body, not in the to_prepare block itself.

Lefthook (trailing newlines, Lychee link check, Prettier) passes locally.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e272eca41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 23, 2026

Code Review

This PR is in good shape after multiple rounds of iteration. The collision explanation is accurate, the Option A/B structure is clear, and the Known Limitations section is comprehensive. One technical gap remains before merge.

Remaining concern: react_on_rails_component has an undocumented bind_call restriction

The Known Limitations section correctly documents that LegacyReactComponent#react_component fails with TypeError in contexts where self doesn't include React::Rails::ViewHelper. But react_on_rails_component has a symmetric restriction that isn't mentioned:

def react_on_rails_component(name, options = {})
  ReactOnRails::Helper.instance_method(:react_component)
                      .bind_call(self, name, options)   # ← requires ReactOnRails::Helper in ancestor chain
end

Because ActionView::Base.include(ReactOnRailsCoexistence) adds react_on_rails_component to all ActionView contexts (including mailer views), the method appears callable — no NameError. But if ReactOnRailsHelper (which includes ReactOnRails::Helper) is not loaded in that view context, bind_call raises TypeError at render time. This is the "method appears available but fails at runtime" failure mode the Known Limitations section exists to warn about.

In practice this bites if someone calls react_on_rails_component from:

  • An Action Mailer view where the mailer doesn't auto-include all app helpers
  • A Rails engine view with restricted helper loading

Suggested addition to Known Limitations:

react_on_rails_component has a symmetric context restriction. Because it uses bind_call against ReactOnRails::Helper, it raises TypeError in any view context where ReactOnRailsHelper is not loaded — for example, Action Mailer views that do not explicitly include app helpers, or engine views with restricted helper loading. Since ActionView::Base.include(ReactOnRailsCoexistence) makes the method appear available in all ActionView contexts, the failure is a runtime TypeError rather than a NameError, so it won't be caught at load time.

Everything else looks good

  • The preflight item 7 anchor resolves correctly to the new section.
  • The to_prepare callback placement is correct and the duplicate-safe note is accurate.
  • The Option A link to #legacy-compatibility-fixes-that-often-make-migration-one-shot matches the heading.
  • The Ruby 2.7+ note for bind_call with the 2.6 fallback is a nice touch.
  • The Zeitwerk note about keeping the module in an initializer (not app/helpers/) is correct and useful.
  • The silent prop-loss case for two-argument legacy calls (addressed in an earlier round) is the most important part of this doc — good that it was caught and corrected.

The missing limitation for react_on_rails_component is the only thing I'd want addressed before merge.

Comment thread docs/oss/migrating/migrating-from-react-rails.md
Comment thread docs/oss/migrating/migrating-from-react-rails.md Outdated
@justin808 justin808 merged commit 3d2aab7 into main Apr 23, 2026
15 of 16 checks passed
@justin808 justin808 deleted the jg/3143-react-rails-helper-collision-docs branch April 23, 2026 07:10
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.

Document or fix helper collision when react_on_rails and react-rails coexist

1 participant