From d34022705090fc6a1c6e813688f589113e640a65 Mon Sep 17 00:00:00 2001 From: Josh LeBlanc Date: Fri, 1 Feb 2019 19:45:27 -0400 Subject: [PATCH 1/7] Mount components when displaying cached page --- react_ujs/src/events/turbolinks.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/react_ujs/src/events/turbolinks.js b/react_ujs/src/events/turbolinks.js index a8d79212..f954281b 100644 --- a/react_ujs/src/events/turbolinks.js +++ b/react_ujs/src/events/turbolinks.js @@ -1,12 +1,12 @@ module.exports = { // Turbolinks 5+ got rid of named events (?!) setup: function(ujs) { - ujs.handleEvent('turbolinks:load', ujs.handleMount) - ujs.handleEvent('turbolinks:before-render', ujs.handleUnmount) + ujs.handleEvent('turbolinks:load', ujs.handleMount); + ujs.handleEvent('turbolinks:before-render', ujs.handleMount); }, teardown: function(ujs) { - ujs.removeEvent('turbolinks:load', ujs.handleMount) - ujs.removeEvent('turbolinks:before-render', ujs.handleUnmount) + ujs.removeEvent('turbolinks:load', ujs.handleMount); + ujs.removeEvent('turbolinks:before-render', ujs.handleMount); }, } From 7ffade89c429803e5ced2bec83f36b36343222ee Mon Sep 17 00:00:00 2001 From: Josh LeBlanc Date: Fri, 1 Feb 2019 19:46:19 -0400 Subject: [PATCH 2/7] Cache rendered components to prevent destroying previously rendered components --- lib/react/rails/component_mount.rb | 4 ++++ react_ujs/index.js | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index 723d108e..b52ebaeb 100644 --- a/lib/react/rails/component_mount.rb +++ b/lib/react/rails/component_mount.rb @@ -15,6 +15,7 @@ class ComponentMount # You can use them in custom helper implementations def setup(controller) @controller = controller + @cache_ids = [] end def teardown(controller) @@ -40,6 +41,9 @@ def react_component(name, props = {}, options = {}, &block) data[:react_class] = name data[:react_props] = (props.is_a?(String) ? props : props.to_json) data[:hydrate] = 't' if prerender_options + + num_components = @cache_ids.count { |c| c.start_with? name } + data[:react_cache_id] = "#{name}-#{num_components}" end end html_tag = html_options[:tag] || :div diff --git a/react_ujs/index.js b/react_ujs/index.js index a2bc3752..64a89cda 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -18,9 +18,14 @@ var ReactRailsUJS = { // This attribute holds which method to use between: ReactDOM.hydrate, ReactDOM.render RENDER_ATTR: 'data-hydrate', + // A unique identifier to identify a node + CACHE_ID_ATTR: "data-react-cache-id", + // If jQuery is detected, save a reference to it for event handlers jQuery: (typeof window !== 'undefined') && (typeof window.jQuery !== 'undefined') && window.jQuery, + components: {}, + // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements findDOMNodes: function(searchSelector) { @@ -85,7 +90,8 @@ var ReactRailsUJS = { var constructor = ujs.getConstructor(className); var propsJson = node.getAttribute(ujs.PROPS_ATTR); var props = propsJson && JSON.parse(propsJson); - var hydrate = node.getAttribute(ujs.RENDER_ATTR); + var hydrate = node.getAttribute(ReactRailsUJS.RENDER_ATTR); + var cacheId = node.getAttribute(ujs.CACHE_ID_ATTR); if (!constructor) { var message = "Cannot find component: '" + className + "'" @@ -94,13 +100,19 @@ var ReactRailsUJS = { } throw new Error(message + ". Make sure your component is available to render.") } else { + let component = this.components[cacheId]; + if(component === undefined) { + component = React.createElement(constructor, props); + this.components[cacheId] = component; + } + if (hydrate && typeof ReactDOM.hydrate === "function") { - ReactDOM.hydrate(React.createElement(constructor, props), node); + component = ReactDOM.hydrate(component, node); } else { - ReactDOM.render(React.createElement(constructor, props), node); + component = ReactDOM.render(component, node); } } - } + } }, // Within `searchSelector`, find nodes which have React components From dd91f28cb1fe9d7e0af3a6ce7aa5172673e26fe0 Mon Sep 17 00:00:00 2001 From: Josh LeBlanc Date: Fri, 1 Feb 2019 20:41:53 -0400 Subject: [PATCH 3/7] move cache_ids initialization to initialize --- lib/react/rails/component_mount.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index b52ebaeb..83bb80e8 100644 --- a/lib/react/rails/component_mount.rb +++ b/lib/react/rails/component_mount.rb @@ -11,11 +11,14 @@ class ComponentMount attr_accessor :output_buffer mattr_accessor :camelize_props_switch + def initialize + @cache_ids = [] + end + # {ControllerLifecycle} calls these hooks # You can use them in custom helper implementations def setup(controller) @controller = controller - @cache_ids = [] end def teardown(controller) From 2b9a0600018aa8be933a8292e354f6268898516b Mon Sep 17 00:00:00 2001 From: Josh LeBlanc Date: Fri, 1 Feb 2019 21:09:30 -0400 Subject: [PATCH 4/7] Added data-react-cache-id to view tests --- test/react/rails/view_helper_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/react/rails/view_helper_test.rb b/test/react/rails/view_helper_test.rb index 977c2dbf..9cdfb546 100644 --- a/test/react/rails/view_helper_test.rb +++ b/test/react/rails/view_helper_test.rb @@ -10,13 +10,13 @@ class ViewHelperHelper class ViewHelperTest < ActionView::TestCase test 'view helper can be called directly' do - expected_html = %{
} + expected_html = %{
} rendered_html = ViewHelperHelper.react_component('Component', { a: 'b' }) assert_equal(expected_html, rendered_html) end test 'view helper accepts block usage' do - expected_html = %{
content
} + expected_html = %{
content
} rendered_html = ViewHelperHelper.react_component('Component', { a: 'b' }) do 'content' end @@ -32,7 +32,7 @@ class ViewHelperTest < ActionView::TestCase test 'view helper can accept block and render inner content only once' do rendered_html = render partial: 'pages/component_with_inner_html' expected_html = < +
NestedContent
HTML From d200040d1cbf2aa37f3808a74a22b7f8ccc239fb Mon Sep 17 00:00:00 2001 From: Josh LeBlanc Date: Fri, 1 Feb 2019 21:15:23 -0400 Subject: [PATCH 5/7] Rearrange html attributes in test --- test/react/rails/view_helper_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/react/rails/view_helper_test.rb b/test/react/rails/view_helper_test.rb index 9cdfb546..a8b13f01 100644 --- a/test/react/rails/view_helper_test.rb +++ b/test/react/rails/view_helper_test.rb @@ -32,7 +32,7 @@ class ViewHelperTest < ActionView::TestCase test 'view helper can accept block and render inner content only once' do rendered_html = render partial: 'pages/component_with_inner_html' expected_html = < +
NestedContent
HTML From fb51da01c2742d4777081207d6c48f4b0aea50a2 Mon Sep 17 00:00:00 2001 From: Josh LeBlanc Date: Sat, 2 Feb 2019 08:18:11 -0400 Subject: [PATCH 6/7] Only cache data-turbolinks-permanent nodes --- react_ujs/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/react_ujs/index.js b/react_ujs/index.js index 64a89cda..f97f6def 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -21,6 +21,8 @@ var ReactRailsUJS = { // A unique identifier to identify a node CACHE_ID_ATTR: "data-react-cache-id", + TURBOLINKS_PERMANENT_ATTR: "data-turbolinks-permanent", + // If jQuery is detected, save a reference to it for event handlers jQuery: (typeof window !== 'undefined') && (typeof window.jQuery !== 'undefined') && window.jQuery, @@ -92,6 +94,7 @@ var ReactRailsUJS = { var props = propsJson && JSON.parse(propsJson); var hydrate = node.getAttribute(ReactRailsUJS.RENDER_ATTR); var cacheId = node.getAttribute(ujs.CACHE_ID_ATTR); + var turbolinksPermanent = node.hasAttribute(ujs.TURBOLINKS_PERMANENT_ATTR); if (!constructor) { var message = "Cannot find component: '" + className + "'" @@ -103,7 +106,9 @@ var ReactRailsUJS = { let component = this.components[cacheId]; if(component === undefined) { component = React.createElement(constructor, props); - this.components[cacheId] = component; + if(turbolinksPermanent) { + this.components[cacheId] = component; + } } if (hydrate && typeof ReactDOM.hydrate === "function") { From 211f18858c2eed6d26c49806cb3b1c210345199a Mon Sep 17 00:00:00 2001 From: Josh LeBlanc Date: Tue, 26 Feb 2019 13:44:52 -0400 Subject: [PATCH 7/7] Be consistent --- react_ujs/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react_ujs/index.js b/react_ujs/index.js index f97f6def..d3e3c907 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -92,7 +92,7 @@ var ReactRailsUJS = { var constructor = ujs.getConstructor(className); var propsJson = node.getAttribute(ujs.PROPS_ATTR); var props = propsJson && JSON.parse(propsJson); - var hydrate = node.getAttribute(ReactRailsUJS.RENDER_ATTR); + var hydrate = node.getAttribute(ujs.RENDER_ATTR); var cacheId = node.getAttribute(ujs.CACHE_ID_ATTR); var turbolinksPermanent = node.hasAttribute(ujs.TURBOLINKS_PERMANENT_ATTR);