diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cf322075..f17f61c1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Changes since last non-beta release. *Please add entries here for your pull requests that are not yet released.* #### Changed +- Document how to manually rehydrate XHR-substituted components on client side. [PR 1095](https://github.com/shakacode/react_on_rails/pull/1095) by [hchevalier](https://github.com/hchevalier). + ### [11.0.7] - 2018-05-11 #### Fixed - Fix npm publshing. [PR 1090](https://github.com/shakacode/react_on_rails/pull/1090) by [justin808](https://github.com/justin808). diff --git a/README.md b/README.md index 529f48b8d..ed2aadba7 100644 --- a/README.md +++ b/README.md @@ -540,7 +540,10 @@ All options except `props, id, html_options` will inherit from your `react_on_ra + **replay_console:** Default is true. False will disable echoing server-rendering logs to the browser. While this can make troubleshooting server rendering difficult, so long as you have the configuration of `logging_on_server` set to true, you'll still see the errors on the server. + **logging_on_server:** Default is true. True will log JS console messages and errors to the server. + **raise_on_prerender_error:** Default is false. True will throw an error on the server side rendering. Your controller will have to handle the error. - + + Note: client hydration will not trigger for components rendered through XHR. You will have to handle it with javascript. + For an example, see [spec/dummy/app/views/pages/xhr_refresh.rb](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/app/views/pages/xhr_refresh.rb). + ### react_component_hash `react_component_hash` is used to return multiple HTML strings for server rendering, such as for adding meta-tags to a page. It is exactly like react_component except for the following: diff --git a/spec/dummy/app/views/pages/_xhr_refresh_partial.html.erb b/spec/dummy/app/views/pages/_xhr_refresh_partial.html.erb new file mode 100644 index 000000000..862aaf31d --- /dev/null +++ b/spec/dummy/app/views/pages/_xhr_refresh_partial.html.erb @@ -0,0 +1,9 @@ +<%= react_component('HelloWorld', props: { helloWorldData: { name: 'HelloWorld' } }, + prerender: true, + trace: true, + id: "HelloWorld-react-component-0") %> + +<%= react_component('HelloWorldRehydratable', props: { helloWorldData: { name: 'HelloWorldRehydratable' } }, + prerender: true, + trace: true, + id: 'HelloWorldRehydratable-react-component-1') %> diff --git a/spec/dummy/app/views/pages/xhr_refresh.html.erb b/spec/dummy/app/views/pages/xhr_refresh.html.erb new file mode 100644 index 000000000..06096d9d7 --- /dev/null +++ b/spec/dummy/app/views/pages/xhr_refresh.html.erb @@ -0,0 +1,70 @@ +
+ <%= render partial: 'xhr_refresh_partial' %> +
+ +
+ Click to refresh components through XHR (first component event handlers won't work anymore)
+ <%= form_tag '/xhr_refresh', method: :get, remote: true, format: :js do %> + <%= submit_tag 'Refresh', id: 'refresh', name: 'refresh' %> + <% end %> +
+
+ +

React Rails Client Rehydration

+

+ This example demonstrates client side manual rehydration after a component replacement through XHR.

+ + The "Refresh" button on this page will trigger an asynchronous refresh of component-container content.
+ Components will be prerendered by the server and inserted in the DOM (spec/dummy/app/views/pages/xhr_refresh.js.erb)
+ No client rehydration will occur, preventing any event handler to be correctly attached

+ + Thus, the onChange handler of the HelloWorld component won't trigger whereas the one from HellowWorldRehydratable will, thanks to the "hydrate" javascript event dispacthed from xhr_refresh.js.erb
+

+ +
+

Setup

+
    +
  1. + Create component source: spec/dummy/client/app/components/HellowWorldRehydratable.jsx +
  2. +
  3. + Expose the HellowWorldRehydratable Component: spec/dummy/client/app/startup/serverRegistration.jsx and spec/dummy/client/app/startup/clientRegistration.jsx +
    +
    +      import HellowWorldRehydratable from '../components/HellowWorldRehydratable';
    +      import ReactOnRails from 'react-on-rails';
    +      ReactOnRails.register({ HellowWorldRehydratable });
    +    
    +
  4. +
  5. + Place the component on the view: spec/dummy/app/views/pages/xhr_refresh.html.erb, making sure it has a parent node easily selectable +
    +
    +      
    + <%%= react_component("HellowWorldRehydratable", props: { helloWorldData: { name: 'HelloWorld' } }, prerender: true, trace: true, id: "HellowWorldRehydratable-react-component-0") %> +
    +
    +
  6. +
  7. + Have a remote form allow to get xhr_request.js.erb +
    +
    +      <%%= form_tag '/xhr_refresh', method: :get, remote: true, format: :js do %>
    +        <%%= submit_tag 'Refresh' %>
    +      <%% end %>
    +    
    +
  8. +
  9. + In your xhr_request.js.erb, replace your container content and dispatch the 'hydrate' event that will be caught by HellowWorldRehydratable event handler +
    +
    +      var container = document.getElementById('component-container');
    +      <%% new_component = react_component("HellowWorldRehydratable", props: { helloWorldData: { name: 'HelloWorld' } }, prerender: true, trace: true, id: "HellowWorldRehydratable-react-component-0") %>
    +      container.innerHTML = "<%%= escape_javascript(new_component) %>";
    +
    +      var event = document.createEvent('Event');
    +      event.initEvent('hydrate', true, true);
    +      document.dispatchEvent(event);
    +    
    +
  10. +
diff --git a/spec/dummy/app/views/pages/xhr_refresh.js.erb b/spec/dummy/app/views/pages/xhr_refresh.js.erb new file mode 100644 index 000000000..0473f504a --- /dev/null +++ b/spec/dummy/app/views/pages/xhr_refresh.js.erb @@ -0,0 +1,6 @@ +var container = document.getElementById('component-container'); +container.innerHTML = "<%= escape_javascript(render partial: 'xhr_refresh_partial') %>"; + +var event = document.createEvent('Event'); +event.initEvent('hydrate', true, true); +document.dispatchEvent(event); diff --git a/spec/dummy/app/views/shared/_header.erb b/spec/dummy/app/views/shared/_header.erb index 604b074bb..39fa1a1b3 100644 --- a/spec/dummy/app/views/shared/_header.erb +++ b/spec/dummy/app/views/shared/_header.erb @@ -56,6 +56,9 @@
  • <%= link_to "render_js only example", render_js_path %>
  • +
  • + <%= link_to "XHR Refresh", xhr_refresh_path %> +
  • <%= link_to "One Page with Many Examples at Once", root_path %>
  • diff --git a/spec/dummy/client/app/components/HelloWorldRehydratable.jsx b/spec/dummy/client/app/components/HelloWorldRehydratable.jsx new file mode 100644 index 000000000..8b6be1d36 --- /dev/null +++ b/spec/dummy/client/app/components/HelloWorldRehydratable.jsx @@ -0,0 +1,83 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import ReactOnRails from 'react-on-rails'; +import RailsContext from './RailsContext'; + +class HelloWorldRehydratable extends React.Component { + + static propTypes = { + helloWorldData: PropTypes.shape({ + name: PropTypes.string, + }).isRequired, + railsContext: PropTypes.object, + }; + + // Not necessary if we only call super, but we'll need to initialize state, etc. + constructor(props) { + super(props); + this.state = props.helloWorldData; + this.setNameDomRef = this.setNameDomRef.bind(this); + this.forceClientHydration = this.forceClientHydration.bind(this); + this.handleChange = this.handleChange.bind(this); + } + + componentDidMount() { + document.addEventListener('hydrate', this.forceClientHydration); + } + + componentWillUnmount() { + document.removeEventListener('hydrate', this.forceClientHydration); + } + + setNameDomRef(nameDomNode) { + this.nameDomRef = nameDomNode; + } + + forceClientHydration() { + const registeredComponentName = 'HelloWorldRehydratable'; + const { railsContext } = this.props; + + // Target all instances of the component in the DOM + 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 += 1) { + const component = match[i]; + // Get component specification