Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Changes since the last non-beta release.

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

#### Fixed
- Preserve default controller layouts for `render component:` after the Rails 8 render pipeline change reported in #1356.

## [3.3.0] - 2026-03-31

#### Added
Expand Down
9 changes: 8 additions & 1 deletion lib/react/rails/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Railtie < ::Rails::Railtie
ActionController::Renderers.add :component do |component_name, options|
renderer = ::React::Rails::ControllerRenderer.new(controller: self)
html = renderer.call(component_name, options)
render_options = options.merge(inline: html)
render_options = Railtie.component_render_options(options, html)
render(render_options)
end
end
Expand Down Expand Up @@ -121,6 +121,13 @@ def self.append_react_build_to_assets_version!(assets, react_build)
versioned_assets.version = [versioned_assets.version, "react-#{react_build}"].compact.join("-")
end

def self.component_render_options(options, html)
render_options = options.merge(inline: html)
return render_options if render_options.key?(:layout)

render_options.merge(layout: true)
end

def self.versioned_assets_for(assets)
return assets if versioned_assets?(assets)

Expand Down
65 changes: 65 additions & 0 deletions test/react/rails/component_renderer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

require "test_helper"

class ComponentRendererController < ActionController::Base
append_view_path File.expand_path("../../dummy/app/views", __dir__)
layout "application"

def default_layout
render component: "TodoList"
end

def explicit_layout_false
render component: "TodoList", layout: false
end
end

class ComponentRendererTest < ActionController::TestCase
tests ComponentRendererController

FakeRenderer = Struct.new(:html, :calls) do
def initialize(html)
super(html, [])
end

def call(component_name, options)
calls << [component_name, options]
html
end
end

setup do
@routes = ActionDispatch::Routing::RouteSet.new
@routes.draw do
get "default_layout", to: "component_renderer#default_layout"
get "explicit_layout_false", to: "component_renderer#explicit_layout_false"
end
end

test "render component uses the current layout by default" do # rubocop:disable Minitest/MultipleAssertions
fake_renderer = FakeRenderer.new("<main>SSR</main>")

React::Rails::ControllerRenderer.stub(:new, ->(*) { fake_renderer }) do
get :default_layout
end

assert_response :success
assert_match(%r{<title>Dummy</title>}, response.body)
assert_match(%r{<main>SSR</main>}, response.body)
assert_equal "TodoList", fake_renderer.calls.dig(0, 0)
end

test "render component preserves explicit layout false" do # rubocop:disable Minitest/MultipleAssertions
fake_renderer = FakeRenderer.new("<main>SSR</main>")

React::Rails::ControllerRenderer.stub(:new, ->(*) { fake_renderer }) do
get :explicit_layout_false
end

assert_response :success
assert_no_match(%r{<title>Dummy</title>}, response.body)
assert_match(%r{<main>SSR</main>}, response.body)
assert_equal "TodoList", fake_renderer.calls.dig(0, 0)
end
end
20 changes: 20 additions & 0 deletions test/react/rails/railtie_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,24 @@ class RailtieTest < ActionDispatch::IntegrationTest
React::Rails::Railtie.append_react_build_to_assets_version!(assets, "development")
end
end

test "component render options default to using the current layout" do
render_options = React::Rails::Railtie.component_render_options({ status: :accepted }, "<div>SSR</div>")

assert_equal "<div>SSR</div>", render_options[:inline]
assert render_options[:layout]
assert_equal :accepted, render_options[:status]
end

test "component render options preserve explicit layout overrides" do
render_options = React::Rails::Railtie.component_render_options({ layout: false }, "<div>SSR</div>")

refute render_options[:layout]
end

test "component render options preserve a named layout override" do
render_options = React::Rails::Railtie.component_render_options({ layout: "admin" }, "<div>SSR</div>")

assert_equal "admin", render_options[:layout]
end
end
Loading