From 6e90c9eb083baa0a62bcdb6bbacd5fb05b103811 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sun, 29 Apr 2018 21:40:36 -1000 Subject: [PATCH 1/2] react_component_hash always has pre-render true * Makes no sense to have react_component_hash not use prerrender. * Fixed issue where checking gem existence. * Added docs for react_component_hash --- README.md | 43 +++++++++++++++++++ lib/react_on_rails/react_on_rails_helper.rb | 30 ++++++++++--- lib/react_on_rails/utils.rb | 4 +- .../dummy/app/views/pages/broken_app.html.erb | 2 +- spec/dummy/app/views/pages/react_helmet.erb | 1 - 5 files changed, 70 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c32e8dd83..61f98496b 100644 --- a/README.md +++ b/README.md @@ -357,10 +357,16 @@ You have 2 ways to specify your React components. You can either register the Re ReactOnRails will automatically detect a registered generator function. Thus, there is no difference between registering a React Component versus a "generator function." +#### react_component_hash for Generator Functions Another reason to use a generator function is that sometimes in server rendering, specifically with React Router, you need to return the result of calling ReactDOMServer.renderToString(element). You can do this by returning an object with the following shape: { renderedHtml, redirectLocation, error }. Make sure you use this function with `react_component_hash`. For server rendering, if you wish to return multiple HTML strings from a generator function, you may return an Object from your generator function with a single top level property of `renderedHtml`. Inside this Object, place a key called `componentHtml`, along with any other needed keys. An example scenario of this is when you are using side effects libraries like [React Helmet](https://github.com/nfl/react-helmet). Your Ruby code will get this Object as a Hash containing keys componentHtml and any other custom keys that you added: + +```js { renderedHtml: { componentHtml, customKey1, customKey2} } +``` + +For details on using react_component_hash with react-helmet, see the docs below for the helper API and [docs/additional-reading/react-helmet.md](../docs/additional-reading/react-helmet.md). ### Rails Context and Generator Functions When you use a "generator function" to create react components (or renderedHtml on the server), or you used shared redux stores, you get two params passed to your function that creates a React component: @@ -535,7 +541,44 @@ 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. + +### 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: + +1. `prerender: true` is automatically added to options, as this method doesn't make sense for + client only rendering. +2. Your JavaScript for server rendering must return an Object for the key `server_rendered_html`. +3. Your view code must expect an object and not a string. + +Here is an example of ERB view code: + +```erb + <% react_helmet_app = react_component_hash("ReactHelmetApp", prerender: true, + props: { helloWorldData: { name: "Mr. Server Side Rendering"}}, + id: "react-helmet-0", trace: true) %> + <% content_for :title do %> + <%= react_helmet_app['title'] %> + <% end %> + <%= react_helmet_app["componentHtml"] %> +``` +And here is the JavaScript code: + +```js +export default (props, _railsContext) => { + const componentHtml = renderToString(); + const helmet = Helmet.renderStatic(); + + const renderedHtml = { + componentHtml, + title: helmet.title.toString(), + }; + return { renderedHtml }; +}; + +``` + ### redux_store #### Controller Extension Include the module `ReactOnRails::Controller` in your controller, probably in ApplicationController. This will provide the following controller method, which you can call in your controller actions: diff --git a/lib/react_on_rails/react_on_rails_helper.rb b/lib/react_on_rails/react_on_rails_helper.rb index 2c08e53f8..37e2279b3 100644 --- a/lib/react_on_rails/react_on_rails_helper.rb +++ b/lib/react_on_rails/react_on_rails_helper.rb @@ -120,17 +120,35 @@ def react_component(component_name, options = {}) else msg = <<-MSG.strip_heredoc - ReactOnRails: server_rendered_html is expected to be a String. If you're trying to - use a generator function to return a Hash to your ruby view code, then use + ReactOnRails: server_rendered_html is expected to be a String for #{component_name}. If you're + trying to use a generator function to return a Hash to your ruby view code, then use react_component_hash instead of react_component and see https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx - for an example of the necessary javascript configuration." + for an example of the JavaScript code." MSG raise ReactOnRails::Error, msg end end + # 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: + # 1. prerender: true is automatically added, as this method doesn't make sense for client only + # rendering. + # 2. Your JavaScript for server rendering must return an Object for the key server_rendered_html. + # 3. Your view code must expect an object and not a string. + # + # Here is an example of the view code: + # <% react_helmet_app = react_component_hash("ReactHelmetApp", prerender: true, + # props: { helloWorldData: { name: "Mr. Server Side Rendering"}}, + # id: "react-helmet-0", trace: true) %> + # <% content_for :title do %> + # <%= react_helmet_app['title'] %> + # <% end %> + # <%= react_helmet_app["componentHtml"] %> + # def react_component_hash(component_name, options = {}) + options[:prerender] = true internal_result = internal_react_component(component_name, options) server_rendered_html = internal_result[:result]["html"] console_script = internal_result[:result]["consoleReplayScript"] @@ -148,9 +166,9 @@ def react_component_hash(component_name, options = {}) ) else msg = <<-MSG.strip_heredoc - Generator function used by react_component_hash is expected to return an Object. See - https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx - for an example of the necessary javascript configuration. + Generator function used by react_component_hash for #{component_name} is expected to return + an Object. See https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx + for an example of the JavaScript code." MSG raise ReactOnRails::Error, msg end diff --git a/lib/react_on_rails/utils.rb b/lib/react_on_rails/utils.rb index c520bbc4a..0b3afc643 100644 --- a/lib/react_on_rails/utils.rb +++ b/lib/react_on_rails/utils.rb @@ -140,11 +140,11 @@ def self.generated_assets_full_path end def self.gem_available?(name) - Gem::Specification.find_by_name(name) + Gem::Specification.find_by_name(name).present? rescue Gem::LoadError false rescue StandardError - Gem.available?(name) + Gem.available?(name).present? end def self.react_on_rails_pro? diff --git a/spec/dummy/app/views/pages/broken_app.html.erb b/spec/dummy/app/views/pages/broken_app.html.erb index c6ec4dbe1..fb0e63cf2 100644 --- a/spec/dummy/app/views/pages/broken_app.html.erb +++ b/spec/dummy/app/views/pages/broken_app.html.erb @@ -1,4 +1,4 @@ -<%= react_component_hash("BrokenApp", prerender: true)['componentHtml'] %> +<%= react_component_hash("BrokenApp")['componentHtml'] %>
diff --git a/spec/dummy/app/views/pages/react_helmet.erb b/spec/dummy/app/views/pages/react_helmet.erb index ca2d37bf3..0b4174f19 100644 --- a/spec/dummy/app/views/pages/react_helmet.erb +++ b/spec/dummy/app/views/pages/react_helmet.erb @@ -1,6 +1,5 @@ <% react_helmet_app = react_component_hash("ReactHelmetApp", - prerender: true, props: { helloWorldData: { name: "Mr. Server Side Rendering"}}, id: "react-helmet-0", trace: true) %> From eeddbdbb0dd0da892383944b91c65f365fa297b0 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Tue, 1 May 2018 09:43:26 -1000 Subject: [PATCH 2/2] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d64d8a5c..6b829ab31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Changes since last non-beta release. - Fix the setup for tests for spec/dummy so they automatically rebuild by correctly setting the source_path in the webpacker.yml - Updated documentation for the testing setup. - Above in [PR 1072](https://github.com/shakacode/react_on_rails/pull/1072) by [justin808](https://github.com/justin808). +- `react_component_hash` has implicit `prerender: true` because it makes no sense to have react_component_hash not use prerrender. Improved docs on `react_component_hash`. Also, fixed issue where checking gem existence. [PR 1077](https://github.com/shakacode/react_on_rails/pull/1077) by [justin808](https://github.com/justin808). ### [11.0.3] - 2018-04-24