From 67d724e3effdf2dbc9c241339c515f034f917d22 Mon Sep 17 00:00:00 2001 From: Judah Meek Date: Tue, 14 May 2024 21:27:08 -0500 Subject: [PATCH] refactor root (un)mount to properly support multiple roots --- CHANGELOG.md | 4 +- Gemfile.lock | 5 ++ gemfiles/shakapacker.gemfile.lock | 5 ++ gemfiles/sprockets_3.gemfile.lock | 5 ++ gemfiles/sprockets_4.gemfile.lock | 5 ++ lib/assets/javascripts/react_ujs.js | 2 +- react-rails.gemspec | 1 + react_ujs/dist/react_ujs.js | 2 +- react_ujs/index.js | 46 ++++++++++++++----- .../app/controllers/application_controller.rb | 2 +- .../app/controllers/counters_controller.rb | 11 +++++ .../app/javascript/components/Counter.js | 23 ++++++++++ .../javascript/controllers/mount_counters.js | 26 +++++++++++ .../dummy/app/javascript/packs/application.js | 21 ++++++--- .../views/counters/create.turbo_stream.erb | 3 ++ test/dummy/app/views/counters/index.html.erb | 10 ++++ test/dummy/config/routes.rb | 1 + test/dummy/package.json | 2 + test/dummy/yarn.lock | 23 ++++++++++ test/react/rails/realtime_update_test.rb | 38 +++++++++++++++ 20 files changed, 212 insertions(+), 23 deletions(-) create mode 100644 test/dummy/app/controllers/counters_controller.rb create mode 100644 test/dummy/app/javascript/components/Counter.js create mode 100644 test/dummy/app/javascript/controllers/mount_counters.js create mode 100644 test/dummy/app/views/counters/create.turbo_stream.erb create mode 100644 test/dummy/app/views/counters/index.html.erb create mode 100644 test/react/rails/realtime_update_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index dabe67415..0bd54ba3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # react-rails -If you need help upgrading `react-rails`, `webpacker` to `shakapacker`, or JS packages, contact justin@shakacode.com. The ShakaCode.com team is helping to maintain this Ruby gem. You also might want to consider the [react_on_rails](https://github.com/shakacode/react_on_rails) gem. +If you need help upgrading `react-rails`, `webpacker` to `shakapacker`, or JS packages, contact justin@shakacode.com. The ShakaCode.com team is helping to maintain this Ruby gem. You also might want to consider the [react_on_rails](https://github.com/shakacode/react_on_rails) gem. [#1209 2.7 Release Discussion](https://github.com/reactjs/react-rails/issues/1209) @@ -12,6 +12,8 @@ _Please add entries here for your pull requests that are not yet released. Inclu #### Fixed - Replaced call to ReactRailsUJS.unmountComponents that was erroneously removed by #1290 in 3.0.0 #1339 +- Prevent roots from being re-created when using React 18 [PR 1305](https://github.com/reactjs/react-rails/pull/1305) by [diogobeda](https://github.com/diogobeda) + ## [3.2.0] - 2024-01-10 #### Changed diff --git a/Gemfile.lock b/Gemfile.lock index e6383b944..930a203c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -237,6 +237,10 @@ GEM thor (1.2.1) tilt (2.1.0) timeout (0.4.1) + turbo-rails (2.0.5) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) websocket (1.2.9) @@ -270,6 +274,7 @@ DEPENDENCIES react-rails! selenium-webdriver test-unit (~> 2.5) + turbo-rails BUNDLED WITH 2.4.9 diff --git a/gemfiles/shakapacker.gemfile.lock b/gemfiles/shakapacker.gemfile.lock index 695ef2f6c..bfada4231 100644 --- a/gemfiles/shakapacker.gemfile.lock +++ b/gemfiles/shakapacker.gemfile.lock @@ -246,6 +246,10 @@ GEM thor (1.2.2) tilt (2.2.0) timeout (0.4.1) + turbo-rails (2.0.5) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) websocket (1.2.10) @@ -280,6 +284,7 @@ DEPENDENCIES selenium-webdriver shakapacker (= 7.2.0) test-unit (~> 2.5) + turbo-rails BUNDLED WITH 2.4.9 diff --git a/gemfiles/sprockets_3.gemfile.lock b/gemfiles/sprockets_3.gemfile.lock index a0d24d1c4..5199577aa 100644 --- a/gemfiles/sprockets_3.gemfile.lock +++ b/gemfiles/sprockets_3.gemfile.lock @@ -245,6 +245,10 @@ GEM thor (1.2.1) tilt (2.0.10) timeout (0.4.1) + turbo-rails (2.0.5) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) @@ -284,6 +288,7 @@ DEPENDENCIES sprockets (~> 3.5) sprockets-rails test-unit (~> 2.5) + turbo-rails turbolinks (~> 5) BUNDLED WITH diff --git a/gemfiles/sprockets_4.gemfile.lock b/gemfiles/sprockets_4.gemfile.lock index bbce0d50d..de4452e0f 100644 --- a/gemfiles/sprockets_4.gemfile.lock +++ b/gemfiles/sprockets_4.gemfile.lock @@ -245,6 +245,10 @@ GEM thor (1.2.1) tilt (2.0.10) timeout (0.4.1) + turbo-rails (2.0.5) + actionpack (>= 6.0.0) + activejob (>= 6.0.0) + railties (>= 6.0.0) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) @@ -284,6 +288,7 @@ DEPENDENCIES sprockets (~> 4.0.x) sprockets-rails test-unit (~> 2.5) + turbo-rails turbolinks (~> 5) BUNDLED WITH diff --git a/lib/assets/javascripts/react_ujs.js b/lib/assets/javascripts/react_ujs.js index 8ad34bc06..4b4ee1308 100644 --- a/lib/assets/javascripts/react_ujs.js +++ b/lib/assets/javascripts/react_ujs.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("react-dom"),require("react-dom/server")):"function"==typeof define&&define.amd?define(["react","react-dom","react-dom/server"],t):"object"==typeof exports?exports.ReactRailsUJS=t(require("react"),require("react-dom"),require("react-dom/server")):e.ReactRailsUJS=t(e.React,e.ReactDOM,e.ReactDOMServer)}(self,((__WEBPACK_EXTERNAL_MODULE__787__,__WEBPACK_EXTERNAL_MODULE__156__,__WEBPACK_EXTERNAL_MODULE__997__)=>(()=>{var __webpack_modules__={634:(e,t,n)=>{"use strict";var o=n(156);t.createRoot=o.createRoot,t.hydrateRoot=o.hydrateRoot},10:(e,t,n)=>{var o=n(787),r=n(156),a=n(997),u=n(230),c=n(121),d=n(831),i=n(37),s=n(39),{supportsHydration:_,reactHydrate:l,createReactRootLike:p}=n(198),f=n(613),v={CLASS_NAME_ATTR:"data-react-class",PROPS_ATTR:"data-react-props",RENDER_ATTR:"data-hydrate",CACHE_ID_ATTR:"data-react-cache-id",TURBOLINKS_PERMANENT_ATTR:"data-turbolinks-permanent",jQuery:"undefined"!=typeof window&&void 0!==window.jQuery&&window.jQuery,components:{},roots:[],findDOMNodes:function(e){var t,n,o=v.CLASS_NAME_ATTR;switch(typeof e){case"undefined":t="["+o+"]",n=document;break;case"object":t="["+o+"]",n=e;break;case"string":t=e+"["+o+"], "+e+" ["+o+"]",n=document}return v.jQuery?v.jQuery(t,n):n.querySelectorAll(t)},getConstructor:c,constructorFromGlobal:c,constructorFromRequireContext:d,constructorFromRequireContextWithGlobalFallback:i,useContext:function(e){this.getConstructor=i(e)},useContexts:function(e){this.getConstructor=s(e)},serverRender:function(e,t,n){var r=this.getConstructor(t),u=o.createElement(r,n);return a[e](u)},mountComponents:function(e){for(var t=v,n=t.findDOMNodes(e),r=0;r{var o=n(528),r=n(921),a=n(228),u=n(724),c=n(968);e.exports=function(e){e.handleEvent&&("undefined"!=typeof Turbolinks&&void 0!==Turbolinks.EVENTS&&c.teardown(e),a.teardown(e),u.teardown(e),r.teardown(e),o.teardown(e)),"addEventListener"in window?(e.handleEvent=function(e,t){document.addEventListener(e,t)},e.removeEvent=function(e,t){document.removeEventListener(e,t)}):(e.handleEvent=function(e,t){window.attachEvent(e,t)},e.removeEvent=function(e,t){window.detachEvent(e,t)}),"undefined"!=typeof Turbolinks&&Turbolinks.supported?void 0!==Turbolinks.EVENTS?c.setup(e):void 0!==Turbolinks.controller?a.setup(e):u.setup(e):"undefined"!=typeof $&&"function"==typeof $.pjax?r.setup(e):o.setup(e)}},528:e=>{e.exports={setup:function(e){"addEventListener"in window?e.handleEvent("DOMContentLoaded",e.handleMount):e.handleEvent("onload",e.handleMount)},teardown:function(e){e.removeEvent("DOMContentLoaded",e.handleMount),e.removeEvent("onload",e.handleMount)}}},921:e=>{e.exports={setup:function(e){e.handleEvent("ready",e.handleMount),e.handleEvent("pjax:end",e.handleMount),e.handleEvent("pjax:beforeReplace",e.handleUnmount)},teardown:function(e){e.removeEvent("ready",e.handleMount),e.removeEvent("pjax:end",e.handleMount),e.removeEvent("pjax:beforeReplace",e.handleUnmount)}}},228:e=>{e.exports={setup:function(e){e.handleEvent("turbolinks:load",e.handleMount)},teardown:function(e){e.removeEvent("turbolinks:load",e.handleMount)}}},968:e=>{e.exports={setup:function(e){e.handleEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)},teardown:function(e){e.removeEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.removeEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)}}},724:e=>{e.exports={setup:function(e){Turbolinks.pagesCached(0),e.handleEvent("page:change",e.handleMount),e.handleEvent("page:receive",e.handleUnmount)},teardown:function(e){e.removeEvent("page:change",e.handleMount),e.removeEvent("page:receive",e.handleUnmount)}}},121:function(module){var topLevel="undefined"==typeof window?this:window;module.exports=function(className){var constructor;return constructor=topLevel[className],constructor||(constructor=eval(className)),constructor&&constructor.default&&(constructor=constructor.default),constructor}},831:e=>{e.exports=function(e){return function(t){var n=t.split("."),o=n.shift(),r=n,a=e("./"+o);return r.forEach((function(e){a=a[e]})),a.__esModule&&(a=a.default),a}}},37:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=r(e);return function(e){var n;try{n=t(e)}catch(t){try{n=o(e)}catch(e){console.error(t),console.error(e)}}return n}}},39:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=e.map((e=>r(e)));return function(e){var n;try{var r,a,u=0;do{r=t[u];try{n=r(e)}catch(e){a||(a=e)}u+=1}while(u{"use strict";n.r(t),n.d(t,{createReactRootLike:()=>_,reactHydrate:()=>s,supportsHydration:()=>i});var o=n(156),r=n.n(o),a=n(613),u=n.n(a);let c=r();if(u())try{c=n(634)}catch(e){c=r()}const d=c;function i(){return"function"==typeof d.hydrate||"function"==typeof d.hydrateRoot}function s(e,t){return"function"==typeof d.hydrateRoot?d.hydrateRoot(e,t):d.hydrate(t,e)}function _(e){return u()?d.createRoot(e):function(e){return{render:t=>d.render(t,e)}}(e)}},613:(e,t,n)=>{var o,r=n(156);o=void 0!==r&&(r.version.split(".")[0]||16)>=18,e.exports=o},787:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__787__},156:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__156__},997:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__997__}},__webpack_module_cache__={};function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__=__webpack_require__(10);return __webpack_exports__})())); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("react-dom"),require("react-dom/server")):"function"==typeof define&&define.amd?define(["react","react-dom","react-dom/server"],t):"object"==typeof exports?exports.ReactRailsUJS=t(require("react"),require("react-dom"),require("react-dom/server")):e.ReactRailsUJS=t(e.React,e.ReactDOM,e.ReactDOMServer)}(self,((__WEBPACK_EXTERNAL_MODULE__787__,__WEBPACK_EXTERNAL_MODULE__156__,__WEBPACK_EXTERNAL_MODULE__997__)=>(()=>{var __webpack_modules__={634:(e,t,n)=>{"use strict";var o=n(156);t.createRoot=o.createRoot,t.hydrateRoot=o.hydrateRoot},10:(e,t,n)=>{var o=n(787),r=n(156),a=n(997),u=n(230),c=n(121),i=n(831),d=n(37),s=n(39),{supportsHydration:_,reactHydrate:l,createReactRootLike:p}=n(198),f=n(613),v={CLASS_NAME_ATTR:"data-react-class",PROPS_ATTR:"data-react-props",RENDER_ATTR:"data-hydrate",CACHE_ID_ATTR:"data-react-cache-id",TURBOLINKS_PERMANENT_ATTR:"data-turbolinks-permanent",jQuery:"undefined"!=typeof window&&void 0!==window.jQuery&&window.jQuery,components:{},roots:[],findDOMNodes:function(e){var t,n,o=v.CLASS_NAME_ATTR;switch(typeof e){case"undefined":t="["+o+"]",n=document;break;case"object":t="["+o+"]",n=e;break;case"string":t=e+"["+o+"], "+e+" ["+o+"]",n=document}return v.jQuery?v.jQuery(t,n):n.querySelectorAll(t)},getConstructor:c,constructorFromGlobal:c,constructorFromRequireContext:i,constructorFromRequireContextWithGlobalFallback:d,useContext:function(e){this.getConstructor=d(e)},useContexts:function(e){this.getConstructor=s(e)},serverRender:function(e,t,n){var r=this.getConstructor(t),u=o.createElement(r,n);return a[e](u)},mountComponents:function(e){for(var t=v,n=t.findDOMNodes(e),r=0;r{var o=n(528),r=n(921),a=n(228),u=n(724),c=n(968);e.exports=function(e){e.handleEvent&&("undefined"!=typeof Turbolinks&&void 0!==Turbolinks.EVENTS&&c.teardown(e),a.teardown(e),u.teardown(e),r.teardown(e),o.teardown(e)),"addEventListener"in window?(e.handleEvent=function(e,t){document.addEventListener(e,t)},e.removeEvent=function(e,t){document.removeEventListener(e,t)}):(e.handleEvent=function(e,t){window.attachEvent(e,t)},e.removeEvent=function(e,t){window.detachEvent(e,t)}),"undefined"!=typeof Turbolinks&&Turbolinks.supported?void 0!==Turbolinks.EVENTS?c.setup(e):void 0!==Turbolinks.controller?a.setup(e):u.setup(e):"undefined"!=typeof $&&"function"==typeof $.pjax?r.setup(e):o.setup(e)}},528:e=>{e.exports={setup:function(e){"addEventListener"in window?e.handleEvent("DOMContentLoaded",e.handleMount):e.handleEvent("onload",e.handleMount)},teardown:function(e){e.removeEvent("DOMContentLoaded",e.handleMount),e.removeEvent("onload",e.handleMount)}}},921:e=>{e.exports={setup:function(e){e.handleEvent("ready",e.handleMount),e.handleEvent("pjax:end",e.handleMount),e.handleEvent("pjax:beforeReplace",e.handleUnmount)},teardown:function(e){e.removeEvent("ready",e.handleMount),e.removeEvent("pjax:end",e.handleMount),e.removeEvent("pjax:beforeReplace",e.handleUnmount)}}},228:e=>{e.exports={setup:function(e){e.handleEvent("turbolinks:load",e.handleMount)},teardown:function(e){e.removeEvent("turbolinks:load",e.handleMount)}}},968:e=>{e.exports={setup:function(e){e.handleEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)},teardown:function(e){e.removeEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.removeEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)}}},724:e=>{e.exports={setup:function(e){Turbolinks.pagesCached(0),e.handleEvent("page:change",e.handleMount),e.handleEvent("page:receive",e.handleUnmount)},teardown:function(e){e.removeEvent("page:change",e.handleMount),e.removeEvent("page:receive",e.handleUnmount)}}},121:function(module){var topLevel="undefined"==typeof window?this:window;module.exports=function(className){var constructor;return constructor=topLevel[className],constructor||(constructor=eval(className)),constructor&&constructor.default&&(constructor=constructor.default),constructor}},831:e=>{e.exports=function(e){return function(t){var n=t.split("."),o=n.shift(),r=n,a=e("./"+o);return r.forEach((function(e){a=a[e]})),a.__esModule&&(a=a.default),a}}},37:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=r(e);return function(e){var n;try{n=t(e)}catch(t){try{n=o(e)}catch(e){console.error(t),console.error(e)}}return n}}},39:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=e.map((e=>r(e)));return function(e){var n;try{var r,a,u=0;do{r=t[u];try{n=r(e)}catch(e){a||(a=e)}u+=1}while(u{"use strict";n.r(t),n.d(t,{createReactRootLike:()=>_,reactHydrate:()=>s,supportsHydration:()=>d});var o=n(156),r=n.n(o),a=n(613),u=n.n(a);let c=r();if(u())try{c=n(634)}catch(e){c=r()}const i=c;function d(){return"function"==typeof i.hydrate||"function"==typeof i.hydrateRoot}function s(e,t){return"function"==typeof i.hydrateRoot?i.hydrateRoot(e,t):i.hydrate(t,e)}function _(e){return u()?i.createRoot(e):function(e){return{render:t=>i.render(t,e)}}(e)}},613:(e,t,n)=>{var o,r=n(156);o=void 0!==r&&(r.version.split(".")[0]||16)>=18,e.exports=o},787:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__787__},156:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__156__},997:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__997__}},__webpack_module_cache__={};function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__=__webpack_require__(10);return __webpack_exports__})())); \ No newline at end of file diff --git a/react-rails.gemspec b/react-rails.gemspec index 474de2e87..b977bfadc 100644 --- a/react-rails.gemspec +++ b/react-rails.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'pry-byebug' s.add_development_dependency 'package_json' s.add_development_dependency 'rails', '~> 7.0.7', '>= 7.0.7.2' + s.add_development_dependency 'turbo-rails' s.add_development_dependency 'minitest-retry' s.add_dependency 'connection_pool' diff --git a/react_ujs/dist/react_ujs.js b/react_ujs/dist/react_ujs.js index 8ad34bc06..4b4ee1308 100644 --- a/react_ujs/dist/react_ujs.js +++ b/react_ujs/dist/react_ujs.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("react-dom"),require("react-dom/server")):"function"==typeof define&&define.amd?define(["react","react-dom","react-dom/server"],t):"object"==typeof exports?exports.ReactRailsUJS=t(require("react"),require("react-dom"),require("react-dom/server")):e.ReactRailsUJS=t(e.React,e.ReactDOM,e.ReactDOMServer)}(self,((__WEBPACK_EXTERNAL_MODULE__787__,__WEBPACK_EXTERNAL_MODULE__156__,__WEBPACK_EXTERNAL_MODULE__997__)=>(()=>{var __webpack_modules__={634:(e,t,n)=>{"use strict";var o=n(156);t.createRoot=o.createRoot,t.hydrateRoot=o.hydrateRoot},10:(e,t,n)=>{var o=n(787),r=n(156),a=n(997),u=n(230),c=n(121),d=n(831),i=n(37),s=n(39),{supportsHydration:_,reactHydrate:l,createReactRootLike:p}=n(198),f=n(613),v={CLASS_NAME_ATTR:"data-react-class",PROPS_ATTR:"data-react-props",RENDER_ATTR:"data-hydrate",CACHE_ID_ATTR:"data-react-cache-id",TURBOLINKS_PERMANENT_ATTR:"data-turbolinks-permanent",jQuery:"undefined"!=typeof window&&void 0!==window.jQuery&&window.jQuery,components:{},roots:[],findDOMNodes:function(e){var t,n,o=v.CLASS_NAME_ATTR;switch(typeof e){case"undefined":t="["+o+"]",n=document;break;case"object":t="["+o+"]",n=e;break;case"string":t=e+"["+o+"], "+e+" ["+o+"]",n=document}return v.jQuery?v.jQuery(t,n):n.querySelectorAll(t)},getConstructor:c,constructorFromGlobal:c,constructorFromRequireContext:d,constructorFromRequireContextWithGlobalFallback:i,useContext:function(e){this.getConstructor=i(e)},useContexts:function(e){this.getConstructor=s(e)},serverRender:function(e,t,n){var r=this.getConstructor(t),u=o.createElement(r,n);return a[e](u)},mountComponents:function(e){for(var t=v,n=t.findDOMNodes(e),r=0;r{var o=n(528),r=n(921),a=n(228),u=n(724),c=n(968);e.exports=function(e){e.handleEvent&&("undefined"!=typeof Turbolinks&&void 0!==Turbolinks.EVENTS&&c.teardown(e),a.teardown(e),u.teardown(e),r.teardown(e),o.teardown(e)),"addEventListener"in window?(e.handleEvent=function(e,t){document.addEventListener(e,t)},e.removeEvent=function(e,t){document.removeEventListener(e,t)}):(e.handleEvent=function(e,t){window.attachEvent(e,t)},e.removeEvent=function(e,t){window.detachEvent(e,t)}),"undefined"!=typeof Turbolinks&&Turbolinks.supported?void 0!==Turbolinks.EVENTS?c.setup(e):void 0!==Turbolinks.controller?a.setup(e):u.setup(e):"undefined"!=typeof $&&"function"==typeof $.pjax?r.setup(e):o.setup(e)}},528:e=>{e.exports={setup:function(e){"addEventListener"in window?e.handleEvent("DOMContentLoaded",e.handleMount):e.handleEvent("onload",e.handleMount)},teardown:function(e){e.removeEvent("DOMContentLoaded",e.handleMount),e.removeEvent("onload",e.handleMount)}}},921:e=>{e.exports={setup:function(e){e.handleEvent("ready",e.handleMount),e.handleEvent("pjax:end",e.handleMount),e.handleEvent("pjax:beforeReplace",e.handleUnmount)},teardown:function(e){e.removeEvent("ready",e.handleMount),e.removeEvent("pjax:end",e.handleMount),e.removeEvent("pjax:beforeReplace",e.handleUnmount)}}},228:e=>{e.exports={setup:function(e){e.handleEvent("turbolinks:load",e.handleMount)},teardown:function(e){e.removeEvent("turbolinks:load",e.handleMount)}}},968:e=>{e.exports={setup:function(e){e.handleEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)},teardown:function(e){e.removeEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.removeEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)}}},724:e=>{e.exports={setup:function(e){Turbolinks.pagesCached(0),e.handleEvent("page:change",e.handleMount),e.handleEvent("page:receive",e.handleUnmount)},teardown:function(e){e.removeEvent("page:change",e.handleMount),e.removeEvent("page:receive",e.handleUnmount)}}},121:function(module){var topLevel="undefined"==typeof window?this:window;module.exports=function(className){var constructor;return constructor=topLevel[className],constructor||(constructor=eval(className)),constructor&&constructor.default&&(constructor=constructor.default),constructor}},831:e=>{e.exports=function(e){return function(t){var n=t.split("."),o=n.shift(),r=n,a=e("./"+o);return r.forEach((function(e){a=a[e]})),a.__esModule&&(a=a.default),a}}},37:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=r(e);return function(e){var n;try{n=t(e)}catch(t){try{n=o(e)}catch(e){console.error(t),console.error(e)}}return n}}},39:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=e.map((e=>r(e)));return function(e){var n;try{var r,a,u=0;do{r=t[u];try{n=r(e)}catch(e){a||(a=e)}u+=1}while(u{"use strict";n.r(t),n.d(t,{createReactRootLike:()=>_,reactHydrate:()=>s,supportsHydration:()=>i});var o=n(156),r=n.n(o),a=n(613),u=n.n(a);let c=r();if(u())try{c=n(634)}catch(e){c=r()}const d=c;function i(){return"function"==typeof d.hydrate||"function"==typeof d.hydrateRoot}function s(e,t){return"function"==typeof d.hydrateRoot?d.hydrateRoot(e,t):d.hydrate(t,e)}function _(e){return u()?d.createRoot(e):function(e){return{render:t=>d.render(t,e)}}(e)}},613:(e,t,n)=>{var o,r=n(156);o=void 0!==r&&(r.version.split(".")[0]||16)>=18,e.exports=o},787:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__787__},156:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__156__},997:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__997__}},__webpack_module_cache__={};function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__=__webpack_require__(10);return __webpack_exports__})())); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("react-dom"),require("react-dom/server")):"function"==typeof define&&define.amd?define(["react","react-dom","react-dom/server"],t):"object"==typeof exports?exports.ReactRailsUJS=t(require("react"),require("react-dom"),require("react-dom/server")):e.ReactRailsUJS=t(e.React,e.ReactDOM,e.ReactDOMServer)}(self,((__WEBPACK_EXTERNAL_MODULE__787__,__WEBPACK_EXTERNAL_MODULE__156__,__WEBPACK_EXTERNAL_MODULE__997__)=>(()=>{var __webpack_modules__={634:(e,t,n)=>{"use strict";var o=n(156);t.createRoot=o.createRoot,t.hydrateRoot=o.hydrateRoot},10:(e,t,n)=>{var o=n(787),r=n(156),a=n(997),u=n(230),c=n(121),i=n(831),d=n(37),s=n(39),{supportsHydration:_,reactHydrate:l,createReactRootLike:p}=n(198),f=n(613),v={CLASS_NAME_ATTR:"data-react-class",PROPS_ATTR:"data-react-props",RENDER_ATTR:"data-hydrate",CACHE_ID_ATTR:"data-react-cache-id",TURBOLINKS_PERMANENT_ATTR:"data-turbolinks-permanent",jQuery:"undefined"!=typeof window&&void 0!==window.jQuery&&window.jQuery,components:{},roots:[],findDOMNodes:function(e){var t,n,o=v.CLASS_NAME_ATTR;switch(typeof e){case"undefined":t="["+o+"]",n=document;break;case"object":t="["+o+"]",n=e;break;case"string":t=e+"["+o+"], "+e+" ["+o+"]",n=document}return v.jQuery?v.jQuery(t,n):n.querySelectorAll(t)},getConstructor:c,constructorFromGlobal:c,constructorFromRequireContext:i,constructorFromRequireContextWithGlobalFallback:d,useContext:function(e){this.getConstructor=d(e)},useContexts:function(e){this.getConstructor=s(e)},serverRender:function(e,t,n){var r=this.getConstructor(t),u=o.createElement(r,n);return a[e](u)},mountComponents:function(e){for(var t=v,n=t.findDOMNodes(e),r=0;r{var o=n(528),r=n(921),a=n(228),u=n(724),c=n(968);e.exports=function(e){e.handleEvent&&("undefined"!=typeof Turbolinks&&void 0!==Turbolinks.EVENTS&&c.teardown(e),a.teardown(e),u.teardown(e),r.teardown(e),o.teardown(e)),"addEventListener"in window?(e.handleEvent=function(e,t){document.addEventListener(e,t)},e.removeEvent=function(e,t){document.removeEventListener(e,t)}):(e.handleEvent=function(e,t){window.attachEvent(e,t)},e.removeEvent=function(e,t){window.detachEvent(e,t)}),"undefined"!=typeof Turbolinks&&Turbolinks.supported?void 0!==Turbolinks.EVENTS?c.setup(e):void 0!==Turbolinks.controller?a.setup(e):u.setup(e):"undefined"!=typeof $&&"function"==typeof $.pjax?r.setup(e):o.setup(e)}},528:e=>{e.exports={setup:function(e){"addEventListener"in window?e.handleEvent("DOMContentLoaded",e.handleMount):e.handleEvent("onload",e.handleMount)},teardown:function(e){e.removeEvent("DOMContentLoaded",e.handleMount),e.removeEvent("onload",e.handleMount)}}},921:e=>{e.exports={setup:function(e){e.handleEvent("ready",e.handleMount),e.handleEvent("pjax:end",e.handleMount),e.handleEvent("pjax:beforeReplace",e.handleUnmount)},teardown:function(e){e.removeEvent("ready",e.handleMount),e.removeEvent("pjax:end",e.handleMount),e.removeEvent("pjax:beforeReplace",e.handleUnmount)}}},228:e=>{e.exports={setup:function(e){e.handleEvent("turbolinks:load",e.handleMount)},teardown:function(e){e.removeEvent("turbolinks:load",e.handleMount)}}},968:e=>{e.exports={setup:function(e){e.handleEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)},teardown:function(e){e.removeEvent(Turbolinks.EVENTS.CHANGE,e.handleMount),e.removeEvent(Turbolinks.EVENTS.BEFORE_UNLOAD,e.handleUnmount)}}},724:e=>{e.exports={setup:function(e){Turbolinks.pagesCached(0),e.handleEvent("page:change",e.handleMount),e.handleEvent("page:receive",e.handleUnmount)},teardown:function(e){e.removeEvent("page:change",e.handleMount),e.removeEvent("page:receive",e.handleUnmount)}}},121:function(module){var topLevel="undefined"==typeof window?this:window;module.exports=function(className){var constructor;return constructor=topLevel[className],constructor||(constructor=eval(className)),constructor&&constructor.default&&(constructor=constructor.default),constructor}},831:e=>{e.exports=function(e){return function(t){var n=t.split("."),o=n.shift(),r=n,a=e("./"+o);return r.forEach((function(e){a=a[e]})),a.__esModule&&(a=a.default),a}}},37:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=r(e);return function(e){var n;try{n=t(e)}catch(t){try{n=o(e)}catch(e){console.error(t),console.error(e)}}return n}}},39:(e,t,n)=>{var o=n(121),r=n(831);e.exports=function(e){var t=e.map((e=>r(e)));return function(e){var n;try{var r,a,u=0;do{r=t[u];try{n=r(e)}catch(e){a||(a=e)}u+=1}while(u{"use strict";n.r(t),n.d(t,{createReactRootLike:()=>_,reactHydrate:()=>s,supportsHydration:()=>d});var o=n(156),r=n.n(o),a=n(613),u=n.n(a);let c=r();if(u())try{c=n(634)}catch(e){c=r()}const i=c;function d(){return"function"==typeof i.hydrate||"function"==typeof i.hydrateRoot}function s(e,t){return"function"==typeof i.hydrateRoot?i.hydrateRoot(e,t):i.hydrate(t,e)}function _(e){return u()?i.createRoot(e):function(e){return{render:t=>i.render(t,e)}}(e)}},613:(e,t,n)=>{var o,r=n(156);o=void 0!==r&&(r.version.split(".")[0]||16)>=18,e.exports=o},787:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__787__},156:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__156__},997:e=>{"use strict";e.exports=__WEBPACK_EXTERNAL_MODULE__997__}},__webpack_module_cache__={};function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.exports}__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__=__webpack_require__(10);return __webpack_exports__})())); \ No newline at end of file diff --git a/react_ujs/index.js b/react_ujs/index.js index ac99ac8d3..2d8a70aaf 100644 --- a/react_ujs/index.js +++ b/react_ujs/index.js @@ -131,11 +131,8 @@ var ReactRailsUJS = { if (hydrate && supportsHydration()) { component = reactHydrate(node, component); } else { - const root = createReactRootLike(node) + const root = this.findOrCreateRoot(node); component = root.render(component); - if(supportsRootApi) { - this.roots.push({"node": node, "root": root}) - } } } } @@ -164,19 +161,44 @@ var ReactRailsUJS = { detectEvents(this) }, - unmountRoot: function(node) { - var targetRoots = this.roots.filter( + findOrCreateRoot: function(node) { + var root = this.findRoot(node); + if (!root) { + root = createReactRootLike(node); + if(supportsRootApi) { + this.roots.push({"node": node, "root": root}) + } + } + + return root; + }, + + findRoot: function(node) { + if (!supportsRootApi) { + return; + } + var rootElement = this.roots.find( function(rootElement) { return rootElement["node"] && (rootElement["node"] === node) } - ) - targetRoots.forEach( + ); + if (rootElement) { + return rootElement["root"]; + } + }, + + unmountRoot: function(node) { + var targetRoot = this.findRoot(node); + if (!targetRoot) { + return; + } + + targetRoot.unmount(); + this.roots = this.roots.filter( function(rootElement) { - rootElement["root"].unmount(); - rootElement["root"] = null; - rootElement["node"] = null; + return rootElement["node"] !== node } - ) + ); } } diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb index 1ff0944d0..e21efdd56 100644 --- a/test/dummy/app/controllers/application_controller.rb +++ b/test/dummy/app/controllers/application_controller.rb @@ -3,5 +3,5 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. - protect_from_forgery with: :exception + # protect_from_forgery with: :exception end diff --git a/test/dummy/app/controllers/counters_controller.rb b/test/dummy/app/controllers/counters_controller.rb new file mode 100644 index 000000000..2d4296db4 --- /dev/null +++ b/test/dummy/app/controllers/counters_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class CountersController < ApplicationController + def index + @counters = [{ name: "Counter 1" }] + end + + def create + @counter = { name: "Counter 2" } + end +end diff --git a/test/dummy/app/javascript/components/Counter.js b/test/dummy/app/javascript/components/Counter.js new file mode 100644 index 000000000..f1bcd6446 --- /dev/null +++ b/test/dummy/app/javascript/components/Counter.js @@ -0,0 +1,23 @@ +var React = require("react"); +var createReactClass = require("create-react-class"); + +module.exports = createReactClass({ + getInitialState: function () { + return { count: 0 }; + }, + handleClick: function () { + this.setState({ count: this.state.count + 1 }); + }, + render: function () { + return ( +
+

+ {this.props.name} - {this.state.count} +

+ +
+ ); + }, +}); diff --git a/test/dummy/app/javascript/controllers/mount_counters.js b/test/dummy/app/javascript/controllers/mount_counters.js new file mode 100644 index 000000000..363687524 --- /dev/null +++ b/test/dummy/app/javascript/controllers/mount_counters.js @@ -0,0 +1,26 @@ +var { Controller } = require("@hotwired/stimulus"); +var ReactRailsUJS = require("react_ujs"); + +module.exports = class extends Controller { + connect() { + this.observeChange(); + } + + disconnect() { + this.observer.disconnect(); + } + + observeChange() { + var element = this.element; + var callback = function (mutationsList, _observer) { + mutationsList.forEach(function (mutation) { + if (mutation.type === "childList") { + ReactRailsUJS.mountComponents(element); + } + }); + }; + + this.observer = new MutationObserver(callback); + this.observer.observe(this.element, { childList: true }); + } +}; diff --git a/test/dummy/app/javascript/packs/application.js b/test/dummy/app/javascript/packs/application.js index bffb39c69..74444a371 100644 --- a/test/dummy/app/javascript/packs/application.js +++ b/test/dummy/app/javascript/packs/application.js @@ -1,8 +1,15 @@ -var ctx = require.context("components", true) -var ReactRailsUJS = require("react_ujs") -ReactRailsUJS.useContext(ctx) -var React = require("react") +require("@hotwired/turbo-rails"); +var { Application } = require("@hotwired/stimulus"); +var MountCountersController = require("../controllers/mount_counters"); -window.GlobalComponent = function(props) { - return React.createElement("h1", null, "Global Component") -} +window.Stimulus = Application.start(); +Stimulus.register("mount-counters", MountCountersController); + +var ctx = require.context("components", true); +var ReactRailsUJS = require("react_ujs"); +ReactRailsUJS.useContext(ctx); +var React = require("react"); + +window.GlobalComponent = function (props) { + return React.createElement("h1", null, "Global Component"); +}; diff --git a/test/dummy/app/views/counters/create.turbo_stream.erb b/test/dummy/app/views/counters/create.turbo_stream.erb new file mode 100644 index 000000000..e0e93eb23 --- /dev/null +++ b/test/dummy/app/views/counters/create.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.append :counters do %> + <%= react_component("Counter", @counter) %> +<% end %> diff --git a/test/dummy/app/views/counters/index.html.erb b/test/dummy/app/views/counters/index.html.erb new file mode 100644 index 000000000..bbac3df62 --- /dev/null +++ b/test/dummy/app/views/counters/index.html.erb @@ -0,0 +1,10 @@ +

React 18 bug reproduction

+ +<%= turbo_frame_tag :counters, data: { controller: "mount-counters" } do %> + <% @counters.each do |counter| %> + <%= react_component("Counter", counter) %> + <% end %> +<% end %> +<%= form_with(url: counters_path, method: :post, data: { turbo: true, turbo_stream: true }) do |form| %> + <%= form.submit "Add counter" %> +<% end %> diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index ef57ce2ac..0c308456b 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -3,6 +3,7 @@ Dummy::Application.routes.draw do get "no-turbolinks", to: "pages#no_turbolinks" resources :pages, only: [:show] + resources :counters, only: %i[create index] resources :server, only: [:show] do collection do get :console_example diff --git a/test/dummy/package.json b/test/dummy/package.json index f0b00f25a..63a49f4c1 100644 --- a/test/dummy/package.json +++ b/test/dummy/package.json @@ -9,6 +9,8 @@ "@babel/preset-react": "^7.17.12", "@babel/preset-typescript": "^7.17.12", "@babel/runtime": "^7.18.3", + "@hotwired/stimulus": "^3.2.2", + "@hotwired/turbo-rails": "^7.3.0", "babel-loader": "^8.2.5", "babel-plugin-macros": "^3.1.0", "compression-webpack-plugin": "^9.2.0", diff --git a/test/dummy/yarn.lock b/test/dummy/yarn.lock index 17a62b91b..f88337521 100644 --- a/test/dummy/yarn.lock +++ b/test/dummy/yarn.lock @@ -1100,6 +1100,24 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@hotwired/stimulus@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608" + integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A== + +"@hotwired/turbo-rails@^7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@hotwired/turbo-rails/-/turbo-rails-7.3.0.tgz#422c21752509f3edcd6c7b2725bbe9e157815f51" + integrity sha512-fvhO64vp/a2UVQ3jue9WTc2JisMv9XilIC7ViZmXAREVwiQ2S4UC7Go8f9A1j4Xu7DBI6SbFdqILk5ImqVoqyA== + dependencies: + "@hotwired/turbo" "^7.3.0" + "@rails/actioncable" "^7.0" + +"@hotwired/turbo@^7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.3.0.tgz#2226000fff1aabda9fd9587474565c9929dbf15d" + integrity sha512-Dcu+NaSvHLT7EjrDrkEmH4qET2ZJZ5IcCWmNXxNQTBwlnE5tBZfN6WxZ842n5cHV52DH/AKNirbPBtcEXDLW4g== + "@jest/schemas@^29.6.0": version "29.6.0" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" @@ -1195,6 +1213,11 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@rails/actioncable@^7.0": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.1.3.tgz#4db480347775aeecd4dde2405659eef74a458881" + integrity sha512-ojNvnoZtPN0pYvVFtlO7dyEN9Oml1B6IDM+whGKVak69MMYW99lC2NOWXWeE3bmwEydbP/nn6ERcpfjHVjYQjA== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" diff --git a/test/react/rails/realtime_update_test.rb b/test/react/rails/realtime_update_test.rb new file mode 100644 index 000000000..6c7f94e70 --- /dev/null +++ b/test/react/rails/realtime_update_test.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "test_helper" + +class RealtimeUpdateTest < ActiveSupport::TestCase + ShakapackerHelpers.when_shakapacker_available do + include Capybara::DSL + + def assert_counter_count(page, timer_name, count) + assert page.has_content?("#{timer_name} - #{count}"), <<~MSG + #{page.body} + #{page.driver.browser.logs.get(:browser).inspect} + MSG + end + + setup do + Capybara.current_driver = Capybara.javascript_driver + ShakapackerHelpers.compile + React::ServerRendering.reset_pool + end + + teardown do + ShakapackerHelpers.clear_shakapacker_packs + end + + test "doesn't re-mount the components trees when mountComponents is called" do + visit "/counters" + + assert_counter_count(page, "Counter 1", 0) + page.click_button "Increment Counter 1" + page.click_button "Add counter" + sleep 0.1 + + assert_counter_count(page, "Counter 1", 1) + assert_counter_count(page, "Counter 2", 0) + end + end +end