Skip to content

Commit 36c4c61

Browse files
committed
should force load react-components which send over turbo-stream
1 parent 36f342c commit 36c4c61

17 files changed

+129
-3
lines changed

Gemfile.development_dependencies

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

2323
gem "amazing_print"
2424

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

lib/react_on_rails/configuration.rb

+7-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ 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+
# forces the loading of React components
44+
force_load: false
4345
)
4446
end
4547

@@ -53,7 +55,8 @@ class Configuration
5355
:server_render_method, :random_dom_id, :auto_load_bundle,
5456
:same_bundle_for_client_and_server, :rendering_props_extension,
5557
:make_generated_server_bundle_the_entrypoint,
56-
:defer_generated_component_packs
58+
:defer_generated_component_packs,
59+
:force_load
5760

5861
# rubocop:disable Metrics/AbcSize
5962
def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
@@ -68,7 +71,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
6871
same_bundle_for_client_and_server: nil,
6972
i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil,
7073
random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
71-
components_subdirectory: nil, auto_load_bundle: nil)
74+
components_subdirectory: nil, auto_load_bundle: nil, force_load: nil)
7275
self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
7376
self.generated_assets_dirs = generated_assets_dirs
7477
self.generated_assets_dir = generated_assets_dir
@@ -106,6 +109,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
106109
self.auto_load_bundle = auto_load_bundle
107110
self.make_generated_server_bundle_the_entrypoint = make_generated_server_bundle_the_entrypoint
108111
self.defer_generated_component_packs = defer_generated_component_packs
112+
self.force_load = force_load
109113
end
110114
# rubocop:enable Metrics/AbcSize
111115

lib/react_on_rails/helper.rb

+8
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,14 @@ def internal_react_component(react_component_name, options = {})
441441
"data-trace" => (render_options.trace ? true : nil),
442442
"data-dom-id" => render_options.dom_id)
443443

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,30 @@
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+
const HelloTurboStream = ({ helloTurboStreamData, railsContext }) => {
8+
const [name, setName] = useState(helloTurboStreamData.name);
9+
const nameDomRef = useRef(null);
10+
11+
const handleChange = () => {
12+
setName(nameDomRef.current.value);
13+
};
14+
15+
return (
16+
<div>
17+
<h3 className={css.brightColor}>Hello, {name}!</h3>
18+
{railsContext && <RailsContext {...{ railsContext }} />}
19+
</div>
20+
);
21+
};
22+
23+
HelloTurboStream.propTypes = {
24+
helloTurboStreamData: PropTypes.shape({
25+
name: PropTypes.string,
26+
}).isRequired,
27+
railsContext: PropTypes.object,
28+
};
29+
30+
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+
describe "'force_load' tag option" 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' == true" 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 "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)