diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index 723d108e7..83bb80e89 100644 --- a/lib/react/rails/component_mount.rb +++ b/lib/react/rails/component_mount.rb @@ -11,6 +11,10 @@ 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) @@ -40,6 +44,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 a2bc3752a..d3e3c9077 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -18,9 +18,16 @@ 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", + + 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, + components: {}, + // helper method for the mount and unmount methods to find the // `data-react-class` DOM elements findDOMNodes: function(searchSelector) { @@ -86,6 +93,8 @@ var ReactRailsUJS = { var propsJson = node.getAttribute(ujs.PROPS_ATTR); var props = propsJson && JSON.parse(propsJson); var hydrate = node.getAttribute(ujs.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 + "'" @@ -94,13 +103,21 @@ 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); + if(turbolinksPermanent) { + 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 diff --git a/react_ujs/src/events/turbolinks.js b/react_ujs/src/events/turbolinks.js index a8d792124..f954281be 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); }, } diff --git a/test/react/rails/view_helper_test.rb b/test/react/rails/view_helper_test.rb index 977c2dbf3..a8b13f017 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 = %{