react-rails icon indicating copy to clipboard operation
react-rails copied to clipboard

Compatibility with Turbo (next version of Turbolinks)

Open dvruette opened this issue 4 years ago • 10 comments

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.

dvruette avatar Feb 25 '21 19:02 dvruette

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.

phoozle avatar Mar 03 '21 00:03 phoozle

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:

100terres avatar Sep 14 '21 16:09 100terres

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 avatar Oct 12 '21 01:10 hbriggs

@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 avatar Oct 12 '21 07:10 dvruette

@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:

  1. I had to declare the props in the controller (hashes sent as props to react_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)
  2. 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
  3. I added a listener to the frame-load event in my application.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 avatar Oct 12 '21 19:10 hbriggs

@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.

dvruette avatar Oct 12 '21 20:10 dvruette

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)

lcampanari avatar Aug 11 '22 16:08 lcampanari

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.

MasahiroMorita avatar Jul 29 '23 07:07 MasahiroMorita

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)
})

mattboldt avatar Oct 15 '23 20:10 mattboldt