Skip to content

Commit 99c6c72

Browse files
author
Nicholas Barone
committed
Add tests, dummy examples, docs, plus a bit of polish
1 parent 1e7ddf2 commit 99c6c72

File tree

8 files changed

+66
-8
lines changed

8 files changed

+66
-8
lines changed

docs/getting-started.md

+18
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ issue.
121121
};
122122
```
123123

124+
- You can pass a block to `react_component`, and it will be provided as the `children` prop to the React component, as a React element:
125+
```ruby
126+
<%= react_component("YourComponent") do %>
127+
<p>Contained HTML<p>
128+
<%= render "your/partial" %>
129+
<% end %>
130+
```
131+
```js
132+
import React from 'react';
133+
134+
export default (props) => {
135+
return () => (
136+
<div>{ props.children }</div>
137+
);
138+
};
139+
```
140+
**Note that this is implementing using React's [dangerouslySetInnerHtml](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html)**, so named because of the exposure to cross-site scripting attacks. You should generally be fine since the HTML is coming from your own code via the Rails rendering engine, but this is sharp knife so please exercise caution :)
141+
124142
See the [View Helpers API](https://www.shakacode.com/react-on-rails/docs/api/view-helpers-api/) for more details on `react_component` and its sibling function `react_component_hash`.
125143

126144
## Globally Exposing Your React Components

lib/react_on_rails/helper.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ def react_component(component_name, options = {}, &block)
110110
# <% end %>
111111
# <%= react_helmet_app["componentHtml"] %>
112112
#
113-
def react_component_hash(component_name, options = {})
113+
def react_component_hash(component_name, options = {}, &block)
114+
(options[:props] ||= {})[:children_html] = capture(&block) if block
114115
options[:prerender] = true
116+
115117
internal_result = internal_react_component(component_name, options)
116118
server_rendered_html = internal_result[:result]["html"]
117119
console_script = internal_result[:result]["consoleReplayScript"]

node_package/src/createReactOutput.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ export default function createReactOutput({
2626
}: CreateParams): CreateReactOutputResult {
2727
const { name, component, renderFunction } = componentObj;
2828

29-
const children = props?.children_html ? React.createElement('div', {dangerouslySetInnerHTML: {__html: props.children_html }}) : null;
30-
3129
if (trace) {
3230
if (railsContext && railsContext.serverSide) {
3331
console.log(`RENDERED ${name} to dom node with id: ${domNodeId}`);
@@ -38,13 +36,20 @@ export default function createReactOutput({
3836
console.log(`RENDERED ${name} to dom node with id: ${domNodeId} with props, railsContext:`,
3937
props, railsContext);
4038
}
39+
40+
if (renderFunction) {
41+
console.log(`${name} is a renderFunction`);
42+
}
4143
}
4244

45+
// Convert any nested content passed to the component into a React element.
46+
const children = props?.children_html ? React.createElement('div', {dangerouslySetInnerHTML: {__html: props.children_html }}) : null;
47+
48+
const createElementFromComponent = (komponent: ReactComponent) => React.createElement(komponent, props, children);
49+
4350
if (renderFunction) {
4451
// Let's invoke the function to get the result
45-
if (trace) {
46-
console.log(`${name} is a renderFunction`);
47-
}
52+
4853
const renderFunctionResult = (component as RenderFunction)(props, railsContext);
4954
if (isServerRenderHash(renderFunctionResult as CreateReactOutputResult)) {
5055
// We just return at this point, because calling function knows how to handle this case and
@@ -70,8 +75,8 @@ work if you return JSX. Update by wrapping the result JSX of ${name} in a fat ar
7075

7176
// If a component, then wrap in an element
7277
const reactComponent = renderFunctionResult as ReactComponent;
73-
return React.createElement(reactComponent, props, children);
78+
return createElementFromComponent(reactComponent);
7479
}
7580
// else
76-
return React.createElement(component as ReactComponent, props, children);
81+
return createElementFromComponent(component as ReactComponent);
7782
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<%= react_component("ChildrenExample", prerender: true, trace: true) do %>
2+
<p>This page demonstrates using Rails to produce content for child nodes of React components</p>
3+
<p>And, just to check that multiple DOM nodes are fine</p>
4+
<% end %>

spec/dummy/app/views/shared/_header.erb

+3
Original file line numberDiff line numberDiff line change
@@ -104,5 +104,8 @@
104104
<li>
105105
<%= link_to "Incorrectly wrapping a pure component in a function", pure_component_wrapped_in_function_path %>
106106
</li>
107+
<li>
108+
<%= link_to "Children Example", children_example_path %>
109+
</li>
107110
</ul>
108111
<hr/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react';
2+
3+
const ComponentWithChildren = ({ children }) => (
4+
<div>
5+
<h1>This is component for testing passing children in from Rails</h1>
6+
{ children }
7+
</div>
8+
);
9+
10+
export default ComponentWithChildren;

spec/dummy/config/routes.rb

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
get "react_helmet_broken" => "pages#react_helmet_broken"
4040
get "broken_app" => "pages#broken_app"
4141
get "image_example" => "pages#image_example"
42+
get "children_example" => "pages#children_example"
4243
get "context_function_return_jsx" => "pages#context_function_return_jsx"
4344
get "pure_component_wrapped_in_function" => "pages#pure_component_wrapped_in_function"
4445
get "turbo_frame_tag_hello_world" => "pages#turbo_frame_tag_hello_world"

spec/dummy/spec/system/integration_spec.rb

+15
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,21 @@ def finished_all_ajax_requests?
284284
end
285285
end
286286

287+
describe "with children", :js do
288+
subject { page }
289+
290+
before { visit "/children_example" }
291+
292+
it "children_example should not have any errors" do
293+
expect(page).to have_text(
294+
"This page demonstrates using Rails to produce content for child nodes of React components"
295+
)
296+
expect(page).to have_text(
297+
"And, just to check that multiple DOM nodes are fine"
298+
)
299+
end
300+
end
301+
287302
describe "use different props for server/client", :js do
288303
subject { page }
289304

0 commit comments

Comments
 (0)