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

Activate component loaded asynchronously #921

Closed
thomassnielsen opened this issue Aug 24, 2017 · 4 comments
Closed

Activate component loaded asynchronously #921

thomassnielsen opened this issue Aug 24, 2017 · 4 comments

Comments

@thomassnielsen
Copy link

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?

@justin808
Copy link
Member

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

@hchevalier
Copy link
Contributor

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.

@justin808
Copy link
Member

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

@justin808
Copy link
Member

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

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

No branches or pull requests

3 participants