Skip to content

Compatibility with Turbo (next version of Turbolinks) #1103

Open

Description

@dvruette

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.

Activity

  1. phoozle commented on Mar 3, 2021

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

  2. 100terres commented on Sep 14, 2021

    @100terres

    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 🙂

  3. hbriggs commented on Oct 12, 2021

    @hbriggs

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

  4. dvruette commented on Oct 12, 2021

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

  5. hbriggs commented on Oct 12, 2021

    @hbriggs

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

  6. dvruette commented on Oct 12, 2021

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

  7. lcampanari commented on Aug 11, 2022

    @lcampanari

    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)
  8. MasahiroMorita commented on Jul 29, 2023

    @MasahiroMorita

    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.

  9. mattboldt commented on Oct 15, 2023

    @mattboldt

    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)
    })
  10. inventionsbyhamid commented on Sep 20, 2024

    @inventionsbyhamid

    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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions