Skip to content

Commit

Permalink
should force load react-components which send over turbo-stream
Browse files Browse the repository at this point in the history
  • Loading branch information
theforestvn88 committed May 22, 2024
1 parent 36f342c commit 36c4c61
Show file tree
Hide file tree
Showing 17 changed files with 129 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Gemfile.development_dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ gem "sprockets", "~> 4.0"

gem "amazing_print"

gem "turbo-rails"

group :development, :test do
gem "listen"
gem "pry"
Expand Down
10 changes: 7 additions & 3 deletions lib/react_on_rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ def self.configuration
i18n_output_format: nil,
components_subdirectory: nil,
make_generated_server_bundle_the_entrypoint: false,
defer_generated_component_packs: true
defer_generated_component_packs: true,
# forces the loading of React components
force_load: false
)
end

Expand All @@ -53,7 +55,8 @@ class Configuration
:server_render_method, :random_dom_id, :auto_load_bundle,
:same_bundle_for_client_and_server, :rendering_props_extension,
:make_generated_server_bundle_the_entrypoint,
:defer_generated_component_packs
:defer_generated_component_packs,
:force_load

# rubocop:disable Metrics/AbcSize
def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
Expand All @@ -68,7 +71,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
same_bundle_for_client_and_server: nil,
i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil,
random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
components_subdirectory: nil, auto_load_bundle: nil)
components_subdirectory: nil, auto_load_bundle: nil, force_load: nil)
self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
self.generated_assets_dirs = generated_assets_dirs
self.generated_assets_dir = generated_assets_dir
Expand Down Expand Up @@ -106,6 +109,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
self.auto_load_bundle = auto_load_bundle
self.make_generated_server_bundle_the_entrypoint = make_generated_server_bundle_the_entrypoint
self.defer_generated_component_packs = defer_generated_component_packs
self.force_load = force_load
end
# rubocop:enable Metrics/AbcSize

Expand Down
8 changes: 8 additions & 0 deletions lib/react_on_rails/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,14 @@ def internal_react_component(react_component_name, options = {})
"data-trace" => (render_options.trace ? true : nil),
"data-dom-id" => render_options.dom_id)

if render_options.force_load
component_specification_tag.concat(
content_tag(:script, %(
ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
).html_safe)
)
end

load_pack_for_generated_component(react_component_name, render_options)
# Create the HTML rendering part
result = server_rendered_react_component(render_options)
Expand Down
4 changes: 4 additions & 0 deletions lib/react_on_rails/react_component/render_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ def logging_on_server
retrieve_configuration_value_for(:logging_on_server)
end

def force_load
retrieve_configuration_value_for(:force_load)
end

def to_s
"{ react_component_name = #{react_component_name}, options = #{options}, request_digest = #{request_digest}"
end
Expand Down
4 changes: 4 additions & 0 deletions node_package/src/ReactOnRails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ ctx.ReactOnRails = {
ClientStartup.reactOnRailsPageLoaded();
},

reactOnRailsComponentLoaded(domId: string): void {
ClientStartup.reactOnRailsComponentLoaded(domId);
},

/**
* Returns CSRF authenticity token inserted by Rails csrf_meta_tags
* @returns String or null
Expand Down
19 changes: 19 additions & 0 deletions node_package/src/clientStartup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,25 @@ export function reactOnRailsPageLoaded(): void {
forEachReactOnRailsComponentRender(context, railsContext);
}

export function reactOnRailsComponentLoaded(domId: string): void {
debugTurbolinks(`reactOnRailsComponentLoaded ${domId}`);

const railsContext = parseRailsContext();

// If no react on rails components
if (!railsContext) return;

const context = findContext();
if (supportsRootApi) {
context.roots = [];
}

const el = document.querySelector(`[data-dom-id=${domId}]`);
if (!el) return;

render(el, context, railsContext);
}

function unmount(el: Element): void {
const domNodeId = domNodeIdForEl(el);
const domNode = document.getElementById(domNodeId);
Expand Down
1 change: 1 addition & 0 deletions node_package/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export interface ReactOnRails {
setOptions(newOptions: {traceTurbolinks: boolean}): void;
reactHydrateOrRender(domNode: Element, reactElement: ReactElement, hydrate: boolean): RenderReturnType;
reactOnRailsPageLoaded(): void;
reactOnRailsComponentLoaded(domId: string): void;
authenticityToken(): string | null;
authenticityHeaders(otherHeaders: { [id: string]: string }): AuthenticityHeaders;
option(key: string): string | number | boolean | undefined;
Expand Down
6 changes: 6 additions & 0 deletions spec/dummy/app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ def data
}.merge(xss_payload)
}

@app_props_hello_from_turbo_stream = {
helloTurboStreamData: {
name: "Mrs. Client Side Rendering From Turbo Stream"
}.merge(xss_payload)
}

@app_props_hello_again = {
helloWorldData: {
name: "Mrs. Client Side Hello Again"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= turbo_frame_tag 'hello-turbo-stream' do %>
<%= button_to "send me hello-turbo-stream component", turbo_stream_send_hello_world_path %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= turbo_stream.update 'hello-turbo-stream' do %>
<%= react_component("HelloTurboStream", props: @app_props_hello_from_turbo_stream, force_load: true) %>
<% end %>
8 changes: 8 additions & 0 deletions spec/dummy/client/app/packs/client-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'jquery';
import 'jquery-ujs';
import '@hotwired/turbo-rails';

import ReactOnRails from 'react-on-rails';

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

import HelloTurboStream from '../startup/HelloTurboStream';

ReactOnRails.setOptions({
traceTurbolinks: true,
turbo: true
});

ReactOnRails.register({
HelloTurboStream
});

ReactOnRails.registerStore({
Expand Down
30 changes: 30 additions & 0 deletions spec/dummy/client/app/startup/HelloTurboStream.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import PropTypes from 'prop-types';
import React from 'react';
import RailsContext from '../components/RailsContext';

import css from '../components/HelloWorld.module.scss';

const HelloTurboStream = ({ helloTurboStreamData, railsContext }) => {
const [name, setName] = useState(helloTurboStreamData.name);
const nameDomRef = useRef(null);

const handleChange = () => {
setName(nameDomRef.current.value);
};

return (
<div>
<h3 className={css.brightColor}>Hello, {name}!</h3>
{railsContext && <RailsContext {...{ railsContext }} />}
</div>
);
};

HelloTurboStream.propTypes = {
helloTurboStreamData: PropTypes.shape({
name: PropTypes.string,
}).isRequired,
railsContext: PropTypes.object,
};

export default HelloTurboStream;
2 changes: 2 additions & 0 deletions spec/dummy/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@
get "image_example" => "pages#image_example"
get "context_function_return_jsx" => "pages#context_function_return_jsx"
get "pure_component_wrapped_in_function" => "pages#pure_component_wrapped_in_function"
get "turbo_frame_tag_hello_world" => "pages#turbo_frame_tag_hello_world"
post "turbo_stream_send_hello_world" => "pages#turbo_stream_send_hello_world"
end
1 change: 1 addition & 0 deletions spec/dummy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@babel/preset-env": "7",
"@babel/preset-react": "^7.10.4",
"@babel/runtime": "7.17.9",
"@hotwired/turbo-rails": "^8.0.4",
"@rescript/react": "^0.10.3",
"babel-loader": "8.2.4",
"babel-plugin-macros": "^3.1.0",
Expand Down
20 changes: 20 additions & 0 deletions spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,26 @@ class PlainReactOnRailsHelper
it { is_expected.not_to include '<span id="App-react-component-0"></span>' }
it { is_expected.to include '<div id="App-react-component-0"></div>' }
end

describe "'force_load' tag option" do
let(:force_load_script) do
%(
ReactOnRails.reactOnRailsComponentLoaded('App-react-component-0');
).html_safe
end

context "with 'force_load' == true" do
subject { react_component("App", force_load: true) }

it { is_expected.to include force_load_script }
end

context "without 'force_load' tag option" do
subject { react_component("App") }

it { is_expected.not_to include force_load_script }
end
end
end

describe "#redux_store" do
Expand Down
10 changes: 10 additions & 0 deletions spec/dummy/spec/system/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ def finished_all_ajax_requests?
end
end

describe "TurboStream send react component", :js do
subject { page }

it "force load hello-world component immediately" do
visit "/turbo_frame_tag_hello_world"
click_on "send me hello-turbo-stream component"
expect(page).to have_text "Hello, Mrs. Client Side Rendering From Turbo Stream!"
end
end

describe "Pages/client_side_log_throw", :ignore_js_errors, :js do
subject { page }

Expand Down
1 change: 1 addition & 0 deletions spec/react_on_rails/react_component/render_options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
replay_console
raise_on_prerender_error
random_dom_id
force_load
].freeze

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

0 comments on commit 36c4c61

Please sign in to comment.