Skip to content

Commit 4f44915

Browse files
committed
should force load react-components which send over turbo-stream
1 parent 1295ce9 commit 4f44915

17 files changed

+142
-4
lines changed

Gemfile.development_dependencies

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ gem "sprockets", "~> 4.0"
2323

2424
gem "amazing_print"
2525

26+
gem "turbo-rails"
27+
2628
group :development, :test do
2729
gem "listen"
2830
gem "pry"

lib/react_on_rails/configuration.rb

+5-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def self.configuration
3939
i18n_output_format: nil,
4040
components_subdirectory: nil,
4141
make_generated_server_bundle_the_entrypoint: false,
42-
defer_generated_component_packs: true
42+
defer_generated_component_packs: true,
43+
force_load: false
4344
)
4445
end
4546

@@ -53,7 +54,8 @@ class Configuration
5354
:server_render_method, :random_dom_id, :auto_load_bundle,
5455
:same_bundle_for_client_and_server, :rendering_props_extension,
5556
:make_generated_server_bundle_the_entrypoint,
56-
:defer_generated_component_packs
57+
:defer_generated_component_packs,
58+
:force_load
5759

5860
# rubocop:disable Metrics/AbcSize
5961
def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
@@ -68,7 +70,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
6870
same_bundle_for_client_and_server: nil,
6971
i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil,
7072
random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
71-
components_subdirectory: nil, auto_load_bundle: nil)
73+
components_subdirectory: nil, auto_load_bundle: nil, force_load: nil)
7274
self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
7375
self.generated_assets_dirs = generated_assets_dirs
7476
self.generated_assets_dir = generated_assets_dir

lib/react_on_rails/helper.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,15 @@ def internal_react_component(react_component_name, options = {})
440440
"data-component-name" => render_options.react_component_name,
441441
"data-trace" => (render_options.trace ? true : nil),
442442
"data-dom-id" => render_options.dom_id)
443-
443+
444+
if render_options.force_load
445+
component_specification_tag.concat(
446+
content_tag(:script, %(
447+
ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
448+
).html_safe)
449+
)
450+
end
451+
444452
load_pack_for_generated_component(react_component_name, render_options)
445453
# Create the HTML rendering part
446454
result = server_rendered_react_component(render_options)

lib/react_on_rails/react_component/render_options.rb

+4
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ def logging_on_server
9191
retrieve_configuration_value_for(:logging_on_server)
9292
end
9393

94+
def force_load
95+
retrieve_configuration_value_for(:force_load)
96+
end
97+
9498
def to_s
9599
"{ react_component_name = #{react_component_name}, options = #{options}, request_digest = #{request_digest}"
96100
end

node_package/src/ReactOnRails.ts

+4
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ ctx.ReactOnRails = {
133133
ClientStartup.reactOnRailsPageLoaded();
134134
},
135135

136+
reactOnRailsComponentLoaded(domId: string): void {
137+
ClientStartup.reactOnRailsComponentLoaded(domId);
138+
},
139+
136140
/**
137141
* Returns CSRF authenticity token inserted by Rails csrf_meta_tags
138142
* @returns String or null

node_package/src/clientStartup.ts

+19
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,25 @@ export function reactOnRailsPageLoaded(): void {
218218
forEachReactOnRailsComponentRender(context, railsContext);
219219
}
220220

221+
export function reactOnRailsComponentLoaded(domId: string): void {
222+
debugTurbolinks(`reactOnRailsComponentLoaded ${domId}`);
223+
224+
const railsContext = parseRailsContext();
225+
226+
// If no react on rails components
227+
if (!railsContext) return;
228+
229+
const context = findContext();
230+
if (supportsRootApi) {
231+
context.roots = [];
232+
}
233+
234+
const el = document.querySelector(`[data-dom-id=${domId}]`);
235+
if (!el) return;
236+
237+
render(el, context, railsContext);
238+
}
239+
221240
function unmount(el: Element): void {
222241
const domNodeId = domNodeIdForEl(el);
223242
const domNode = document.getElementById(domNodeId);

node_package/src/types/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export interface ReactOnRails {
126126
setOptions(newOptions: {traceTurbolinks: boolean}): void;
127127
reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType;
128128
reactOnRailsPageLoaded(): void;
129+
reactOnRailsComponentLoaded(domId: string): void;
129130
authenticityToken(): string | null;
130131
authenticityHeaders(otherHeaders: { [id: string]: string }): AuthenticityHeaders;
131132
option(key: string): string | number | boolean | undefined;

spec/dummy/app/controllers/pages_controller.rb

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ def data
3636
}.merge(xss_payload)
3737
}
3838

39+
@app_props_hello_from_turbo_stream = {
40+
helloTurboStreamData: {
41+
name: "Mrs. Client Side Rendering From Turbo Stream"
42+
}.merge(xss_payload)
43+
}
44+
3945
@app_props_hello_again = {
4046
helloWorldData: {
4147
name: "Mrs. Client Side Hello Again"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<%= turbo_frame_tag 'hello-turbo-stream' do %>
2+
<%= button_to "send me hello-turbo-stream component", turbo_stream_send_hello_world_path %>
3+
<% end %>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<%= turbo_stream.update 'hello-turbo-stream' do %>
2+
<%= react_component("HelloTurboStream", props: @app_props_hello_from_turbo_stream, force_load: true) %>
3+
<% end %>

spec/dummy/client/app/packs/client-bundle.js

+8
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@ import 'core-js/stable';
22
import 'regenerator-runtime/runtime';
33
import 'jquery';
44
import 'jquery-ujs';
5+
import '@hotwired/turbo-rails';
56

67
import ReactOnRails from 'react-on-rails';
78

89
import SharedReduxStore from '../stores/SharedReduxStore';
910

11+
import HelloTurboStream from '../startup/HelloTurboStream';
12+
1013
ReactOnRails.setOptions({
1114
traceTurbolinks: true,
15+
turbo: true
16+
});
17+
18+
ReactOnRails.register({
19+
HelloTurboStream
1220
});
1321

1422
ReactOnRails.registerStore({
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import RailsContext from '../components/RailsContext';
4+
5+
import css from '../components/HelloWorld.module.scss';
6+
7+
class HelloTurboStream extends React.Component {
8+
static propTypes = {
9+
helloTurboStreamData: PropTypes.shape({
10+
name: PropTypes.string,
11+
}).isRequired,
12+
railsContext: PropTypes.object,
13+
};
14+
15+
constructor(props) {
16+
super(props);
17+
this.state = props.helloTurboStreamData;
18+
this.setNameDomRef = this.setNameDomRef.bind(this);
19+
this.handleChange = this.handleChange.bind(this);
20+
}
21+
22+
setNameDomRef(nameDomNode) {
23+
this.nameDomRef = nameDomNode;
24+
}
25+
26+
handleChange() {
27+
const name = this.nameDomRef.value;
28+
this.setState({ name });
29+
}
30+
31+
render() {
32+
const { name } = this.state;
33+
const { railsContext } = this.props;
34+
35+
return (
36+
<div>
37+
<h3 className={css.brightColor}>Hello, {name}!</h3>
38+
{railsContext && <RailsContext {...{ railsContext }} />}
39+
</div>
40+
);
41+
}
42+
}
43+
44+
export default HelloTurboStream;

spec/dummy/config/routes.rb

+2
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@
4141
get "image_example" => "pages#image_example"
4242
get "context_function_return_jsx" => "pages#context_function_return_jsx"
4343
get "pure_component_wrapped_in_function" => "pages#pure_component_wrapped_in_function"
44+
get "turbo_frame_tag_hello_world" => "pages#turbo_frame_tag_hello_world"
45+
post "turbo_stream_send_hello_world" => "pages#turbo_stream_send_hello_world"
4446
end

spec/dummy/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"@babel/preset-env": "7",
1313
"@babel/preset-react": "^7.10.4",
1414
"@babel/runtime": "7.17.9",
15+
"@hotwired/turbo-rails": "^8.0.4",
1516
"@rescript/react": "^0.10.3",
1617
"babel-loader": "8.2.4",
1718
"babel-plugin-macros": "^3.1.0",

spec/dummy/spec/helpers/react_on_rails_helper_spec.rb

+20
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,26 @@ class PlainReactOnRailsHelper
288288
it { is_expected.not_to include '<span id="App-react-component-0"></span>' }
289289
it { is_expected.to include '<div id="App-react-component-0"></div>' }
290290
end
291+
292+
context "force load" do
293+
let(:force_load_script) do
294+
%(
295+
ReactOnRails.reactOnRailsComponentLoaded('App-react-component-0');
296+
).html_safe
297+
end
298+
299+
context "with 'force_load' tag option" do
300+
subject { react_component("App", force_load: true) }
301+
302+
it { is_expected.to include force_load_script }
303+
end
304+
305+
context "without 'force_load' tag option" do
306+
subject { react_component("App") }
307+
308+
it { is_expected.not_to include force_load_script }
309+
end
310+
end
291311
end
292312

293313
describe "#redux_store" do

spec/dummy/spec/system/integration_spec.rb

+10
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ def finished_all_ajax_requests?
9797
end
9898
end
9999

100+
describe "TurboStream send react component", :js do
101+
subject { page }
102+
103+
it "should force load hello-world component immediately" do
104+
visit "/turbo_frame_tag_hello_world"
105+
click_on "send me hello-turbo-stream component"
106+
expect(page).to have_text "Hello, Mrs. Client Side Rendering From Turbo Stream!"
107+
end
108+
end
109+
100110
describe "Pages/client_side_log_throw", :ignore_js_errors, :js do
101111
subject { page }
102112

spec/react_on_rails/react_component/render_options_spec.rb

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
replay_console
1010
raise_on_prerender_error
1111
random_dom_id
12+
force_load
1213
].freeze
1314

1415
def the_attrs(react_component_name: "App", options: {})

0 commit comments

Comments
 (0)