-
-
Notifications
You must be signed in to change notification settings - Fork 638
Allow rendering of React components without using a generator function (just direct React Component) #19
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
Allow rendering of React components without using a generator function (just direct React Component) #19
Changes from all commits
809c2fc
7b12680
be7abb7
1a19be0
ca81794
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,18 +28,18 @@ def react_component(component_name, props = {}, options = {}) | |
| prerender = options.fetch(:prerender) { ReactOnRails.configuration.prerender } | ||
| trace = options.fetch(:trace, false) | ||
|
|
||
| dataVariable = "__#{component_name.camelize(:lower)}Data#{@react_component_index}__" | ||
| reactComponent = component_name.camelize | ||
| domId = "#{component_name}-react-component-#{@react_component_index}" | ||
| data_variable = "__#{component_name.camelize(:lower)}Data#{@react_component_index}__" | ||
| react_component_name = component_name.camelize | ||
| dom_id = "#{component_name}-react-component-#{@react_component_index}" | ||
| @react_component_index += 1 | ||
|
|
||
| turbolinks_loaded = Object.const_defined?(:Turbolinks) | ||
| install_render_events = turbolinks_loaded ? turbolinks_bootstrap(domId) : non_turbolinks_bootstrap | ||
| install_render_events = turbolinks_loaded ? turbolinks_bootstrap(dom_id) : non_turbolinks_bootstrap | ||
|
|
||
| page_loaded_js = <<-JS | ||
| (function() { | ||
| window.#{dataVariable} = #{props.to_json}; | ||
| #{define_render_if_dom_node_present(reactComponent, dataVariable, domId, trace)} | ||
| window.#{data_variable} = #{props.to_json}; | ||
| #{define_render_if_dom_node_present(react_component_name, data_variable, dom_id, trace)} | ||
| #{install_render_events} | ||
| })(); | ||
| JS | ||
|
|
@@ -49,7 +49,7 @@ def react_component(component_name, props = {}, options = {}) | |
| # Create the HTML rendering part | ||
| if prerender | ||
| render_js_expression = <<-JS | ||
| renderReactComponent(this.#{reactComponent}, #{props.to_json}) | ||
| this.React.renderToString(#{render_js_react_element(react_component_name, props.to_json)}); | ||
| JS | ||
| server_rendered_react_component_html = render_js(render_js_expression) | ||
| else | ||
|
|
@@ -58,7 +58,7 @@ def react_component(component_name, props = {}, options = {}) | |
|
|
||
| rendered_output = content_tag(:div, | ||
| server_rendered_react_component_html, | ||
| id: domId) | ||
| id: dom_id) | ||
|
|
||
| <<-HTML.strip_heredoc.html_safe | ||
| #{data_from_server_script_tag} | ||
|
|
@@ -76,24 +76,28 @@ def render_js(js_expression) | |
|
|
||
| private | ||
|
|
||
| def debug_js(react_component, data_variable, dom_id, trace) | ||
| def debug_js(react_component_name, data_variable, dom_id, trace) | ||
| if trace | ||
| <<-JS.strip_heredoc | ||
| console.log("CLIENT SIDE RENDERED #{react_component} with dataVariable #{data_variable} to dom node with id: #{dom_id}"); | ||
| console.log("CLIENT SIDE RENDERED #{react_component_name} with data_variable #{data_variable} to dom node with id: #{dom_id}"); | ||
| JS | ||
| else | ||
| "" | ||
| end | ||
| end | ||
|
|
||
| def define_render_if_dom_node_present(react_component, data_variable, dom_id, trace) | ||
| def render_js_react_element(react_component_name, props_name) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's change |
||
| ReactOnRails::ReactRenderer.render_js_react_element(react_component_name, props_name) | ||
| end | ||
|
|
||
| def define_render_if_dom_node_present(react_component_name, data_variable, dom_id, trace) | ||
| <<-JS.strip_heredoc | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move this method over to lib/react_on_rails/react_renderer.rb. Then we can reuse the common lines from 96 to 103. The below code has the var names hard coded as var element;
if (Object.getPrototypeOf(reactComponent) === this.React.Component) {
element = this.React.createElement(reactComponent, props);
} else {
// when using Redux, we need to pull the component off the wrapper function
element = reactComponent(props);
} |
||
| var renderIfDomNodePresent = function() { | ||
| var domNode = document.getElementById('#{dom_id}'); | ||
| if (domNode) { | ||
| #{debug_js(react_component, data_variable, dom_id, trace)} | ||
| var reactComponent = #{react_component}(#{data_variable}); | ||
| React.render(reactComponent, domNode); | ||
| #{debug_js(react_component_name, data_variable, dom_id, trace)} | ||
| var reactElement = #{render_js_react_element(react_component_name, data_variable)}; | ||
| React.render(reactElement, domNode); | ||
| } | ||
| } | ||
| JS | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,21 @@ | ||
| module ReactOnRails | ||
| class ReactRenderer | ||
|
|
||
| # "this" does not need a closure as it refers to the "this" defined by the | ||
| # calling the calling context which is the "this" in the execJs environment. | ||
| def render_js_react_component | ||
| # Returns the JavaScript code to generate a React element. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's put back this comment. |
||
| # The parameter react_component_name can be a React component or a generator function | ||
| # that returns a React component. To be invoked as a function, react_component_name | ||
| # must have the property "generator" set to true and be a function that | ||
| # takes one parameter, props, that is used to construct the React component. | ||
| def self.render_js_react_element(react_component_name, props_name) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change props_name to props_string. Let's put this comment: |
||
| <<-JS.strip_heredoc | ||
| function renderReactComponent(componentClass, props) { | ||
| return this.React.renderToString( | ||
| componentClass(props) | ||
| ); | ||
| } | ||
| #{react_component_name}.generator ? | ||
| #{react_component_name}(#{props_name}) : | ||
| this.React.createElement(#{react_component_name}, #{props_name}) | ||
| JS | ||
| end | ||
|
|
||
| def initialize | ||
| js_code = "#{bundle_js_code};\n#{render_js_react_component}" | ||
| js_code = "#{bundle_js_code};" | ||
| @context = ExecJS.compile(js_code) | ||
| end | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,6 @@ | ||
| <%= render "header" %> | ||
|
|
||
| <h1>React Rails Server Rendering</h1> | ||
|
|
||
| <hr/> | ||
|
|
||
| <h1>Simple Client Rendered Component</h1> | ||
| <!-- Passing prerender: false to not render on server --> | ||
| <code> | ||
| <%%= react_component("HelloWorldComponent", @app_props_hello, prerender: false, trace: true) %> | ||
| </code> | ||
| <%= react_component("HelloWorldComponent", @app_props_hello, prerender: false, trace: true) %> | ||
| <hr/> | ||
|
|
||
| <h1>Showing you can put the same component twice on a page with different props</h1> | ||
| <code> | ||
| <%%= react_component("HelloWorldComponent", @app_props_hello_again, prerender: false, trace: true) %> | ||
| </code> | ||
|
|
||
| <%= react_component("HelloWorldComponent", @app_props_hello_again, prerender: false, trace: true) %> | ||
|
|
||
| <hr/> | ||
|
|
||
| <h1>Server Rendered/Cached React/Redux Component</h1> | ||
|
|
@@ -28,6 +10,7 @@ | |
| <br/><b>WARNING: be sure to clear the cache by opening a console and running Rails.cache.clear</b> | ||
| <% end %> | ||
| </p> | ||
|
|
||
| <code> | ||
| <%% cache @app_props_server_render do %><br/> | ||
| <%% = react_component("App", @app_props_server_render, trace: true) %><br/> | ||
|
|
@@ -39,16 +22,52 @@ | |
|
|
||
| <% cache @app_props_server_render do %> | ||
| <% puts "=" * 80 %> | ||
| <% puts "server rendering react component" %> | ||
| <% puts "server rendering react components" %> | ||
| <% puts "=" * 80 %> | ||
| <!-- Default for prerender is true for the app, set in config/react_on_rails.rb --> | ||
| <%= react_component("App", @app_props_server_render, trace: true) %> | ||
| <hr/> | ||
|
|
||
| <h1>Server Rendered/Cached React Component Without Redux</h1> | ||
| <code> | ||
| <%% cache @app_props_server_render do %><br/> | ||
| <%% = react_component("HelloWorld", @app_props_server_render, trace: true) %><br/> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's confusing that we call it HelloWorld in one case, and HelloWorldComponent in another case. Why is this done? Maybe I'm missing something. Be sure to note that ClientApp.jsx is ONLY loaded client side in the browser, and ServerApp.jsx is ONLY loaded server side by the rails server, so there would be no naming conflict. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @justin808 HelloWorldComponent is a function that returns the component There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should make the component generator "function" name more explicit. Like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 for |
||
| <%% end %> | ||
| </code> | ||
| <p> | ||
| And this is an example of a server rendered React component without Redux | ||
| </p> | ||
|
|
||
| <%= react_component("HelloWorld", @app_props_server_render, trace: true) %> | ||
| <% end %> | ||
| <hr/> | ||
|
|
||
| <h1>Simple Client Rendered Component</h1> | ||
| <!-- Passing prerender: false to not render on server --> | ||
| <code> | ||
| <%%= react_component("HelloWorldApp", @app_props_hello, prerender: false, trace: true) %> | ||
| </code> | ||
| <%= react_component("HelloWorldApp", @app_props_hello, prerender: false, trace: true) %> | ||
| <hr/> | ||
|
|
||
| <h1>Showing you can put the same component twice on a page with different props</h1> | ||
| <code> | ||
| <%%= react_component("HelloWorldApp", @app_props_hello_again, prerender: false, trace: true) %> | ||
| </code> | ||
| <%= react_component("HelloWorldApp", @app_props_hello_again, prerender: false, trace: true) %> | ||
| <hr/> | ||
|
|
||
| <h1>Simple Component Without Redux</h1> | ||
| <code> | ||
| <%%= react_component("HelloWorld", @app_props_hello, prerender: false, trace: true) %> | ||
| <%%= react_component("HelloES5", @app_props_hello, prerender: false, trace: true) %> | ||
| </code> | ||
| <%= react_component("HelloWorld", @app_props_hello, prerender: false, trace: true) %> | ||
| <%= react_component("HelloES5", @app_props_hello, prerender: false, trace: true) %> | ||
| <hr/> | ||
|
|
||
| <h1>Non-React Component</h1> | ||
| For example, Suppose you have some JavaScript that generates a string of HTML: | ||
| <br/> | ||
| <p>For example, Suppose you have some JavaScript that generates a string of HTML:</p> | ||
| <code> | ||
| this.HelloString.world() | ||
| </code> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import React from 'react'; | ||
|
|
||
| // Super simple example of React component using React.createClass | ||
| const HelloES5 = React.createClass({ | ||
|
|
||
| getInitialState() { | ||
| return this.props.helloWorldData; | ||
| }, | ||
|
|
||
| _handleChange() { | ||
| const name = React.findDOMNode(this.refs.name).value; | ||
| this.setState({name}); | ||
| }, | ||
|
|
||
| render() { | ||
| const { name } = this.state; | ||
|
|
||
| return ( | ||
| <div> | ||
| <h3> | ||
| Hello ES5, {name}! | ||
| </h3> | ||
| <p> | ||
| Say hello to: | ||
| <input type="text" ref="name" defaultValue={name} onChange={this._handleChange} /> | ||
| </p> | ||
| </div> | ||
| ); | ||
| }, | ||
| }); | ||
|
|
||
| export default HelloES5; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,20 @@ | ||
| // Top level component for simple client side only rendering | ||
|
|
||
| import React from 'react'; | ||
|
|
||
| import HelloWorld from '../components/HelloWorld'; | ||
|
|
||
| /* | ||
| * Export a function that takes the props and returns a ReactComponent. | ||
| * This is used for the client rendering hook after the page html is rendered. | ||
| * React will see that the state is the same and not do anything. | ||
| */ | ||
| window.HelloWorldComponent = props => { | ||
| window.HelloWorldApp = props => { | ||
| return <HelloWorld {...props}/>; | ||
| }; | ||
|
|
||
| /* | ||
| * If you wish to create a React component via a function, rather than simply props, | ||
| * then you need to set the property "generator" on that function to true. | ||
| * When that is done, the function is invoked with a single parameter of "props", | ||
| * and that function should return a react element. | ||
| */ | ||
| window.HelloWorldApp.generator = true; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,14 +3,26 @@ | |
| // Shows the mapping from the exported object to the name used by the server rendering. | ||
| import App from './ServerApp'; | ||
|
|
||
| // Example of server rendering without using Redux | ||
| import { HelloWorld, HelloES5 } from './ServerApp'; | ||
|
|
||
| // Example of server rendering with no React | ||
| import HelloString from '../non_react/HelloString'; | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good job cleaning up out of date docs! |
||
| /* | ||
| * If you wish to create a React component via a function, rather than simply props, | ||
| * then you need to set the property "generator" on that function to true. | ||
| * When that is done, the function is invoked with a single parameter of "props", | ||
| * and that function should return a react element. | ||
| */ | ||
| App.generator = true; | ||
|
|
||
| // We can use the node global object for exposing. | ||
| // NodeJs: https://nodejs.org/api/globals.html#globals_global | ||
| // Uncomment next 4 lines to use global | ||
| global.HelloString = HelloString; | ||
| global.App = App; | ||
| global.HelloWorld = HelloWorld; | ||
| global.HelloES5 = HelloES5; | ||
| global.HelloString = HelloString; | ||
|
|
||
| // Alternative syntax for exposing Vars | ||
| // require("expose?HelloString!./non_react/HelloString.js"); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add comment above this line: