Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with Turbo (next version of Turbolinks) #1103

Open
dvruette opened this issue Feb 25, 2021 · 10 comments
Open

Compatibility with Turbo (next version of Turbolinks) #1103

dvruette opened this issue Feb 25, 2021 · 10 comments

Comments

@dvruette
Copy link

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.

@phoozle
Copy link

phoozle commented Mar 3, 2021

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.

@100terres
Copy link

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 hotwired/turbo#327 🙂

@hbriggs
Copy link

hbriggs commented Oct 12, 2021

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

@dvruette
Copy link
Author

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

@hbriggs
Copy link

hbriggs commented Oct 12, 2021

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

@dvruette
Copy link
Author

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

@lcampanari
Copy link

lcampanari commented Aug 11, 2022

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)

@MasahiroMorita
Copy link

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.

@mattboldt
Copy link

In case anyone runs into this on v3+, since components are no longer automatically unmounted here:

ReactRailsUJS.handleUnmount = function(e) {
var target = undefined;
if (e && e.target) {
target = e.target;
}
}

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

@inventionsbyhamid
Copy link

I added below lines after going through comments

ReactRailsUJS.handleEvent('turbo:load', ReactRailsUJS.handleMount);
ReactRailsUJS.handleEvent('turbo:frame-load', ReactRailsUJS.handleMount, false);
ReactRailsUJS.handleEvent('turbo:before-render', ReactRailsUJS.handleUnmount);

This causes components to render but components are mounted/rendered multiple times. For example using useEffect hook with empty array dependency is called thrice for me.

Is anyone working to add turbo support directly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants