renchap/webpacker-react

Components don't re-mount after Turbolinks navigation

Opened this issue ยท 15 comments

According to this line a react component will only mount if the innerHTML is empty.

// javascript/webpacker_react-npm-module/src/index.js:72
if (node.innerHTML.length === 0) this.render(node, component)

This works well for initial page loads that are not rendered via the Turbolinks cache, but components will not re-mount after navigating around an app that utilize Turbolinks caching. This can be replicated with the following steps:

  1. Use react_component on page one which mount's just fine.
  2. Click link to visit page two. page one's html is cached, the component is unmounted and page two is rendered.
  3. Click the back button, page one is rendered from the Turbolinks cache and the component will not be re-mounted as the components innerHTML is not empty

This is not noticeable for simple components that render static text, but becomes immediately apparent if a component that changes state over time (showing the current time, polling for new data, etc.).

Is there a reason for ensuring innerHTML is empty before mounting a component? Or perhaps the components should be un-mounted on turbolinks:before-cache to avoid being cached all together?

Thanks for the report @phil-monroe!

@sevos do you remember why this check was needed?

Hi, I'm new to React and I'm trying to render a component in my layout file. I'm not sure if this issue is related to what I'm facing. The components do render when I visit different links but I'm getting the following error every time I visit another link:

unmountComponentAtNode(): The node you're attempting to unmount was rendered by another copy of React.

In my layout file, I am rendering a simple component:

/ application.html.slim
/ left out some code...
= javascript_pack_tag 'admin'
= react_component('Header')

Here's in the root pack that I'm calling:

// app/javascript/packs/root_pack.js
import Header from './header/header';
import Turbolinks from 'turbolinks';
Turbolinks.start();

WebpackerReact.setup({Header});

Obviously, the error disappears when I remove turbolinks. I think that it's great having it there, I feel that the page loads sluggishly without it ;)

Appreciate the help!

sevos commented

What versions of the gem and npm package are you using? @igikorn

sevos commented

@renchap I have no idea why we have this line there. Imho it shouldn't be there, but I may be wrong.

@sevos Thanks for the reply!

Yarn

"webpack": "^2.3.3",
"webpack-dev-server": "^2.4.2",
"webpack-manifest-plugin": "^1.1.0",
"webpack-merge": "^4.1.0",
"webpacker-react": "^0.2.1"

Gemfile

webpacker (1.1)
webpacker-react (0.2.0)

sevos commented

Could you please provide a minimum repository reproducing the problem? I will take a second look in the evening

Hi @sevos, I've reproduced the issue in a minimum repository:

https://github.com/igikorn/webpacker-react-turbolinks

Thank you very much!

Hi @sevos any luck with this? Appreciate the help!

@igikorn can you tell me if https://github.com/shakacode/react_on_rails has the same Turbolinks issue?

Hi,

Uncaught Error: Cannot find module "turbolinks"

This app is really helpful. I am facing an issue with turbolinks adding to component.

import Hello from 'components/hello'
import WebpackerReact from 'webpacker-react'
// import Turbolinks from 'turbolinks'

// Turbolinks.start()

WebpackerReact.setup({Hello})

Thanks

@zeeshan-za-ahmad @renchap
I think I can help with this one.
I released a new version of react_UJS (The NPM module) that fixes a Turbolinks 5 issue.
reactjs/react-rails#868

I've released it as the --pre tag waiting on a JQuery confirmation but this may fix your issue.

@justin808 It's rude to go into other people's repos and pitch your gem, please try to HELP people if you're visiting other projects.

Hey, I've bumped into this issue as well.
For me, I found that the easiest way to handle this is to just let turbolinks know that it shouldn't cache pages that include React components with state by including something like

<% content_for :head do %>
  <meta name="turbolinks-cache-control" content="no-cache">
<% end %>
<%= react_component('Component', data: @data) %>

So this way the navigation still occurss with turbolinks, but it won't display a cached versions of the page (which renders the components Html but isn't handled by React AFAIK) bur rather hit the server and re mount the components just like on first visit.

Any news on this issue? I've tried the "no-cache" approach but it won't work for me. Emptied the cache, still nothing. The component just won't mount after a Turbolinks navigation. ๐Ÿ˜•

EDIT: seems to be working when I use ReactRailsUJS.detectEvents() (after useContext in my case).

You could try disabling Turbolinks Page cache globally, and see if the cache is actually the reason why your react component is not getting mounted.

<meta name="turbolinks-cache-control" content="no-cache">

Someone just needs to port over the detection fixes from React_UJS to this NPM package.
reactjs/react-rails#868