react-rails
react-rails copied to clipboard
Compatibility with Turbo (next version of Turbolinks)
Issue
The next version of Turbolinks, now called Turbo (available for Rails via turbo-rails), is in beta and react-rails
should be updated to be compatible. The new interface is similar to the old one, the events are just called differently.
The relevant events are now called turbo:load
and turbo:before-render
instead of turbolinks:load
and turbolinks:before-render
. A new event script along the following lines should do the trick:
// in react_ujs/src/events/turbo.js
module.exports = {
setup: function(ujs) {
ujs.handleEvent('turbo:load', ujs.handleMount);
ujs.handleEvent('turbo:before-render', ujs.handleUnmount);
},
teardown: function(ujs) {
ujs.removeEvent('turbo:load', ujs.handleMount);
ujs.removeEvent('turbo:before-render', ujs.handleUnmount);
},
}
Along with this some changes to the detection script will be required, but I'm not familiar enough to be able to tell exactly what needs to be added.
Hotfix
I have solved this issue in my app by adding the following two lines to the application.js
script:
// earlier: var ujs = require("react_ujs");
ujs.handleEvent('turbo:load', ujs.handleMount);
ujs.handleEvent('turbo:before-render', ujs.handleUnmount);
For anyone already using Turbo and looking to also use react-rails
, the above is a hotfix until it's implemented in the package.
Thanks to the contributors for taking a look at this.
Another thing to consider is turbo-frame events once that is merged https://github.com/hotwired/turbo/pull/59
At the moment React components that come in via a frame do not mount even with the above hotfix. And as per the above PR there is currently no existing events to tap into.
Another thing to consider is turbo-frame events once that is merged hotwired/turbo#59
At the moment React components that come in via a frame do not mount even with the above hotfix. And as per the above PR there is currently no existing events to tap into.
@phoozle They've added turbo:frame-render
event https://github.com/hotwired/turbo/pull/327 :slightly_smiling_face:
Has anyone gotten this to work recently? I have the latest version (v7.0.1) installed locally and am still unable to render react-rails
components in a turbo frame...
@hbriggs Unfortunately no.
Personally, I’ve started using Stimulus components whenever possible, since they work seamlessly even within turbo frames. It’s a compromise, since Stimulus doesn’t allow for easy DOM manipulation, but I’ve been able to make due so far.
@dvruette thanks the for the response! Stimulus is the goal for me too. Unfortunately we have a lot of random react components that we can't immediately migrate so I was hoping to shim them into turbo frames until we have time to do that.
I got the components we were rendering inside of stuff that I moved to a turbo frame to work by doing three things:
- I had to declare the
props
in the controller (hashes sent as props toreact_component
didn't seem to be rendering as expected within a turbo frame and I got parsing errors; just passing@props
solved the parsing issue) - I needed to change some of the links inside of react-rendered components to have
target='_top'
so that they don't get commandeered by turbo - I added a listener to the
frame-load
event in myapplication.js
:
document.addEventListener('turbo:frame-load', function (_e) {
ReactRailsUJS.mountComponents();
}, false);
I'm not sure if this is the cleanest way to do this but haven't found anything else so far...
@hbriggs Nice, thanks for letting me know! Good to know that there is a way now, sometimes I'd prefer to use (or reuse) React components, especially when there are lots of DOM manipulations and Stimulus isn't really a good option.
Would also be great to get this working out of the box, might take a stab at it if I find the time.
Got it to work for turbo-frame events:
// app/javascript/packs/application.js
var ReactRailsUJS = require('react_ujs')
ReactRailsUJS.handleEvent('turbo:frame-load', ReactRailsUJS.handleMount)
ReactRailsUJS.handleEvent('turbo:frame-render', ReactRailsUJS.handleUnmount)
After installing react-rails and turbo-rails, I encountered the following problem.
Versions and configuration
-
react-rails
: 2.7.0 -
turbo-rails
: 1.4.0 -
app/javascript/packs/application.js
: add the following snippet.
var ReactRailsUJS = require("react_ujs");
ReactRailsUJS.handleEvent('turbo:load', ReactRailsUJS.handleMount)
ReactRailsUJS.handleEvent('turbo:before-render', ReactRailsUJS.handleUnmount)
Problem. 1
After implementing useMediaQuery responsive support, the following exception occurs when changing the screen size.
Uncaught runtime errors:
ERROR
Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
at removeChildFromContainer (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:66940:15)
at commitDeletionEffectsOnFiber (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:79856:15)
at recursivelyTraverseDeletionEffects (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:79819:5)
at commitDeletionEffectsOnFiber (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:79948:9)
at commitDeletionEffects (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:79806:5)
at recursivelyTraverseMutationEffects (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:80089:9)
at commitMutationEffectsOnFiber (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:80123:9)
at recursivelyTraverseMutationEffects (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:80103:7)
at commitMutationEffectsOnFiber (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:80123:9)
at recursivelyTraverseMutationEffects (http://localhost:3000/packs/js/vendors-node_modules_hotwired_turbo-rails_app_javascript_turbo_index_js-node_modules_mui_mate-95ab8d.js:80103:7)
Problem. 2
The following error occurs with each page transition.
react-dom.development.js:86 Warning: You are calling ReactDOM.unmountComponentAtNode() on a container that was previously passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?
Hotfix for problem 1
The first problem can be avoided by changing the previous snippet as follows:
// Do not call 'handlerMount()' right after the page is first loaded,
// but when the page is subsequently rewritten by turbo-rails.
var skipFirstCall = false
ReactRailsUJS.handleEvent('turbo:load', ()=> {
skipFirstCall && ReactRailsUJS.handleMount()
skipFirstCall = true
})
Hotfix for problem 2
The second problem can be avoided by removing the following code:
ReactRailsUJS.handleEvent('turbo:before-render', ReactRailsUJS.handleUnmount)
I don't think this essentially solves the problem, but it solves the glitch anyway. I really hope that you will be able to properly resolve the compatibility issue with turbo-rails.
In case anyone runs into this on v3+, since components are no longer automatically unmounted here: https://github.com/reactjs/react-rails/blob/0930f24b0d6869f36b30a5cf853ee92aa9306d20/react_ujs/index.js#L191-L196
I got it to work by calling unmountComponents
directly, e.g.:
const componentRequireContext = require.context('components', true)
const ReactRailsUJS = require('react_ujs')
ReactRailsUJS.useContext(componentRequireContext)
// Prevent double mount on page load
ReactRailsUJS._firstTurboLoadSkipped = false
ReactRailsUJS.handleEvent('turbo:load', () => {
if (ReactRailsUJS._firstTurboLoadSkipped) ReactRailsUJS.handleMount()
ReactRailsUJS._firstTurboLoadSkipped = true
})
// Unmount components and call cleanup functions after Turbo navigations
ReactRailsUJS.handleEvent('turbo:before-render', (e) => {
ReactRailsUJS.unmountComponents(e.target)
})