React_on_rails: Activate component loaded asynchronously

Created on 24 Aug 2017  Â·  4Comments  Â·  Source: shakacode/react_on_rails

We have a legacy system where we load parts of a form asynchronously. Now we want to replace parts of that form with react components. However, if I use react_component in a template that's loaded via an xhr request, it doesn't activate. If I use prerender: true the component renders, but it isn't rehydrated.

Is there anything I can call to activate the component from the template?

Most helpful comment

@thomassnielsen Try out: https://github.com/shakacode/react_on_rails/blob/master/node_package/src/ReactOnRails.js#L83

If you don't want to reload everything, see what that function calls. Feel free to ask me to re-open this.

All 4 comments

@thomassnielsen Try out: https://github.com/shakacode/react_on_rails/blob/master/node_package/src/ReactOnRails.js#L83

If you don't want to reload everything, see what that function calls. Feel free to ask me to re-open this.

Hi,

I may have missed something but I couldn’t find a way to use ClientStartup.render without modifying the node module, and neither could I re-render all of my components, so I came with another solution:

In a component:

componentDidMount () {
  // If several Ajax calls can lead to different components needing an hydration cycle, you can set a more specific event name, e.g. 'hydratemycomponent’.
  document.addEventListener('hydrate', this.forceClientHydration)
}

componentWillUnmount () {
  document.removeEventListener('hydrate', this.forceClientHydration)
}

forceClientHydration = () => {
  const registeredComponentName = 'MyRegisteredComponent' // from startup/registration.jsx
  // I use current (synchronous) railsContext, mind using an updated version if your ajax call returns a new one
  const { railsContext } = this.props

  // Target all instances of the component
  const match = document.querySelectorAll(`[id^=${registeredComponentName}-react-component-]`)
  // Not all browsers support forEach on NodeList so we go with a classic for-loop
  for (let i = 0; i < match.length; ++i) {
    const component = match[i]
    // Get <script> tag, which is the previous element sibling (this avoids Gecko empty "Text" nodes)
    const componentScript = component.previousElementSibling
    // Read props from the script tag and merge railsContext
    const mergedProps = {...JSON.parse(componentScript.textContent), railsContext}
    // Hydrate, you'll have to import ReactOnRails from 'react-on-rails'
    ReactOnRails.render(registeredComponentName, mergedProps, component.id)
  }
}

To trigger it from your ajax call callback:

var event = document.createEvent('Event')
event.initEvent('hydrate', true, true)
document.dispatchEvent(event)

Also, if you use Redux, make sure the redux_store from your controller is also called when serving async requests, I wasted some time on this one.

@hchevalier if you think this is generally useful, please create a very simple demonstration of why in the sample app in the /spec/dummy of this repo. Throw some docs, a changelog entry, and a test, and I'll merge it!

@hchevalier Please email me at [email protected] to get an invite to our Slack channel.

Was this page helpful?
0 / 5 - 0 ratings