Skip to content

Commit 5cf69c1

Browse files
authored
Fix default layout handling for render component (#1418)
* Fix default layout handling for render component * Refine component renderer helper usage * Address render component review feedback * Document component_render_options as internal API
1 parent ce863e0 commit 5cf69c1

4 files changed

Lines changed: 115 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Changes since the last non-beta release.
1515

1616
_Please add entries here for your pull requests that have not yet been released. Include LINKS for PRs and committers._
1717

18+
#### Fixed
19+
- Preserve default controller layouts for `render component:` after the Rails 8 render pipeline change. [PR 1418](https://github.com/reactjs/react-rails/pull/1418) by [justin808](https://github.com/justin808). Fixes [#1356](https://github.com/reactjs/react-rails/issues/1356).
20+
1821
## [3.3.0] - 2026-03-31
1922

2023
#### Added

lib/react/rails/railtie.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class Railtie < ::Rails::Railtie
6666
ActionController::Renderers.add :component do |component_name, options|
6767
renderer = ::React::Rails::ControllerRenderer.new(controller: self)
6868
html = renderer.call(component_name, options)
69-
render_options = options.merge(inline: html)
69+
render_options = Railtie.component_render_options(options, html)
7070
render(render_options)
7171
end
7272
end
@@ -121,6 +121,14 @@ def self.append_react_build_to_assets_version!(assets, react_build)
121121
versioned_assets.version = [versioned_assets.version, "react-#{react_build}"].compact.join("-")
122122
end
123123

124+
# :nodoc:
125+
def self.component_render_options(options, html)
126+
render_options = options.merge(inline: html)
127+
return render_options if render_options.key?(:layout)
128+
129+
render_options.merge(layout: true)
130+
end
131+
124132
def self.versioned_assets_for(assets)
125133
return assets if versioned_assets?(assets)
126134

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class ComponentRendererController < ActionController::Base
6+
append_view_path File.expand_path("../../dummy/app/views", __dir__)
7+
layout "application"
8+
9+
def default_layout
10+
render component: "TodoList"
11+
end
12+
13+
def explicit_layout_false
14+
render component: "TodoList", layout: false
15+
end
16+
17+
def explicit_named_layout
18+
render component: "TodoList", layout: "app_no_turbolinks"
19+
end
20+
end
21+
22+
class ComponentRendererTest < ActionController::TestCase
23+
tests ComponentRendererController
24+
25+
FakeRenderer = Struct.new(:html, :calls) do
26+
def initialize(html)
27+
super(html, [])
28+
end
29+
30+
def call(component_name, options)
31+
calls << [component_name, options]
32+
html
33+
end
34+
end
35+
36+
setup do
37+
@routes = ActionDispatch::Routing::RouteSet.new
38+
@routes.draw do
39+
get "default_layout", to: "component_renderer#default_layout"
40+
get "explicit_layout_false", to: "component_renderer#explicit_layout_false"
41+
get "explicit_named_layout", to: "component_renderer#explicit_named_layout"
42+
end
43+
end
44+
45+
test "render component uses the current layout by default" do # rubocop:disable Minitest/MultipleAssertions
46+
fake_renderer = FakeRenderer.new("<main>SSR</main>")
47+
48+
React::Rails::ControllerRenderer.stub(:new, ->(*) { fake_renderer }) do
49+
get :default_layout
50+
end
51+
52+
assert_response :success
53+
assert_match(%r{<title>Dummy</title>}, response.body)
54+
assert_match(%r{<main>SSR</main>}, response.body)
55+
assert_equal "TodoList", fake_renderer.calls.dig(0, 0)
56+
end
57+
58+
test "render component preserves explicit layout false" do # rubocop:disable Minitest/MultipleAssertions
59+
fake_renderer = FakeRenderer.new("<main>SSR</main>")
60+
61+
React::Rails::ControllerRenderer.stub(:new, ->(*) { fake_renderer }) do
62+
get :explicit_layout_false
63+
end
64+
65+
assert_response :success
66+
assert_no_match(%r{<title>Dummy</title>}, response.body)
67+
assert_match(%r{<main>SSR</main>}, response.body)
68+
assert_equal "TodoList", fake_renderer.calls.dig(0, 0)
69+
end
70+
71+
test "render component preserves a named layout override" do # rubocop:disable Minitest/MultipleAssertions
72+
fake_renderer = FakeRenderer.new("<main>SSR</main>")
73+
74+
React::Rails::ControllerRenderer.stub(:new, ->(*) { fake_renderer }) do
75+
get :explicit_named_layout
76+
end
77+
78+
assert_response :success
79+
assert_match(/app_no_turbolinks/, response.body)
80+
assert_match(%r{<main>SSR</main>}, response.body)
81+
assert_equal "TodoList", fake_renderer.calls.dig(0, 0)
82+
end
83+
end

test/react/rails/railtie_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,24 @@ class RailtieTest < ActionDispatch::IntegrationTest
4545
React::Rails::Railtie.append_react_build_to_assets_version!(assets, "development")
4646
end
4747
end
48+
49+
test "component render options default to using the current layout" do
50+
render_options = React::Rails::Railtie.component_render_options({ status: :accepted }, "<div>SSR</div>")
51+
52+
assert_equal "<div>SSR</div>", render_options[:inline]
53+
assert_same true, render_options[:layout]
54+
assert_equal :accepted, render_options[:status]
55+
end
56+
57+
test "component render options preserve explicit layout overrides" do
58+
render_options = React::Rails::Railtie.component_render_options({ layout: false }, "<div>SSR</div>")
59+
60+
assert_same false, render_options[:layout]
61+
end
62+
63+
test "component render options preserve a named layout override" do
64+
render_options = React::Rails::Railtie.component_render_options({ layout: "admin" }, "<div>SSR</div>")
65+
66+
assert_equal "admin", render_options[:layout]
67+
end
4868
end

0 commit comments

Comments
 (0)