Skip to content

Commit 43169ad

Browse files
Add usage guide for React on Rails render functions and tests for serverRenderReactComponent
1 parent 7b8dfc2 commit 43169ad

File tree

2 files changed

+386
-11
lines changed

2 files changed

+386
-11
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# React on Rails Render Functions: Usage Guide
2+
3+
Based on the test file `serverRenderReactComponent.test.ts` and the existing documentation, I'll clarify how render functions work in React on Rails and how to use them with each Ruby helper method.
4+
5+
## Types of Render Functions and Their Return Values
6+
7+
Looking at the test file, render functions can return several types of values:
8+
9+
### 1. React Components (JSX)
10+
11+
```jsx
12+
const MyComponent = (props, _railsContext) => {
13+
return <div>Hello {props.name}</div>;
14+
};
15+
```
16+
17+
### 2. Objects with renderedHtml Property
18+
19+
```jsx
20+
const MyComponent = (props, _railsContext) => {
21+
return {
22+
renderedHtml: `<div>Hello ${props.name}</div>`,
23+
};
24+
};
25+
```
26+
27+
### 3. Objects with Multiple HTML Properties
28+
29+
```jsx
30+
const MyComponent = (props, _railsContext) => {
31+
return {
32+
componentHtml: <div>Hello {props.name}</div>,
33+
title: `<title>${props.title}</title>`,
34+
metaTags: `<meta name="description" content="${props.description}" />`,
35+
};
36+
};
37+
```
38+
39+
### 4. Promises of Strings
40+
41+
```jsx
42+
const MyComponent = async (props, _railsContext) => {
43+
const data = await fetchData();
44+
return `<div>Hello ${data.name}</div>`;
45+
};
46+
```
47+
48+
### 5. Promises of Objects
49+
50+
```jsx
51+
const MyComponent = async (props, _railsContext) => {
52+
const data = await fetchData();
53+
return {
54+
componentHtml: `<div>Hello ${data.name}</div>`,
55+
};
56+
};
57+
```
58+
59+
### 6. Redirect Information
60+
61+
```jsx
62+
const MyComponent = (props, _railsContext) => {
63+
return {
64+
redirectLocation: '/new-path',
65+
error: null,
66+
renderedHtml: null,
67+
};
68+
};
69+
```
70+
71+
## Important Limitations Observed in Tests
72+
73+
The test file reveals some important limitations:
74+
75+
1. **Direct String Returns Don't Work** - Returning a raw HTML string directly from a render function causes an error. The test `doesn't render html string returned directly from render function` demonstrates this.
76+
77+
2. **Non-Promise Objects Need renderedHtml** - Non-promise objects must include a `renderedHtml` property to be valid, as shown in the test `doesn't render object without renderedHtml property`.
78+
79+
3. **Async Functions Have Different Behavior** - Interestingly, the test `returns the object returned by async render function even if it doesn't have renderedHtml property` shows that Promise-returning functions can return objects without a `renderedHtml` property and they will still work.
80+
81+
## Ruby Helper Functions with Render Functions
82+
83+
### 1. react_component
84+
85+
The `react_component` helper is used for rendering a single React component. It accepts various return types from render functions:
86+
87+
```ruby
88+
# Basic usage with a component
89+
<%= react_component("MyComponent", props: { name: "John" }) %>
90+
91+
# With a render function that returns JSX
92+
<%= react_component("MyRenderFunction", props: { name: "John" }) %>
93+
94+
# With a render function that returns an object with renderedHtml
95+
<%= react_component("MyRenderFunction", props: { name: "John" }) %>
96+
97+
# With a render function that returns a Promise
98+
<%= react_component("MyAsyncRenderFunction", props: { name: "John" }) %>
99+
```
100+
101+
#### When to use:
102+
103+
- When you need to render a single component
104+
- When you're rendering client-side only
105+
- When your render function returns a single HTML string
106+
107+
#### Not suitable for:
108+
109+
- When your render function returns an object with multiple HTML strings
110+
- When you need to insert content in different parts of the page
111+
112+
### 2. react_component_hash
113+
114+
The `react_component_hash` helper is used when your render function returns an object with multiple HTML strings. It allows you to place different parts of the rendered output in different parts of your layout.
115+
116+
```ruby
117+
# With a render function that returns an object with multiple HTML properties
118+
<% helmet_data = react_component_hash("HelmetComponent", props: {
119+
title: "My Page",
120+
description: "Page description"
121+
}) %>
122+
123+
<% content_for :head do %>
124+
<%= helmet_data["title"] %>
125+
<%= helmet_data["metaTags"] %>
126+
<% end %>
127+
128+
<div class="main-content">
129+
<%= helmet_data["componentHtml"] %>
130+
</div>
131+
```
132+
133+
#### When to use:
134+
135+
- When your render function returns multiple HTML strings in an object
136+
- When you need to insert rendered content in different parts of your page
137+
- For SEO-related rendering like meta tags and title tags
138+
- When working with libraries like React Helmet
139+
140+
#### Not suitable for:
141+
142+
- Simple component rendering
143+
- Client-side only rendering (always uses server rendering)
144+
145+
#### Requirements:
146+
147+
- The render function MUST return an object
148+
- The object MUST include a `componentHtml` key
149+
- All other keys are optional and can be accessed in your Rails view
150+
151+
## Best Practices Based on the Tests
152+
153+
1. **Always Use Objects for Multiple HTML Parts**: If you need multiple HTML strings, return an object with named properties, and use `react_component_hash` to access them.
154+
155+
2. **Don't Return Raw HTML Strings**: Tests show that returning a raw HTML string directly causes errors - either use JSX or wrap in a `{ renderedHtml: '...' }` object.
156+
157+
3. **Async Functions Work with Objects**: For async render functions, you can return both strings and objects (with or without a `renderedHtml` property).
158+
159+
4. **Use Redirect Object Format**: For redirects, return an object with `{ redirectLocation, error, renderedHtml: null }`.
160+
161+
## Example: Different Return Types with Appropriate Helper Methods
162+
163+
### Return Type 1: React Component
164+
165+
```jsx
166+
// JavaScript
167+
const SimpleComponent = (props) => <div>Hello {props.name}</div>;
168+
ReactOnRails.register({ SimpleComponent });
169+
```
170+
171+
```erb
172+
<%# Ruby - Either helper works %>
173+
<%= react_component("SimpleComponent", props: { name: "John" }) %>
174+
```
175+
176+
### Return Type 2: Object with renderedHtml
177+
178+
```jsx
179+
// JavaScript
180+
const RenderedHtmlComponent = (props) => {
181+
return { renderedHtml: `<div>Hello ${props.name}</div>` };
182+
};
183+
ReactOnRails.register({ RenderedHtmlComponent });
184+
```
185+
186+
```erb
187+
<%# Ruby - Either helper works %>
188+
<%= react_component("RenderedHtmlComponent", props: { name: "John" }) %>
189+
```
190+
191+
### Return Type 3: Object with Multiple HTML Properties
192+
193+
```jsx
194+
// JavaScript
195+
const HelmetComponent = (props) => {
196+
return {
197+
componentHtml: <div>Hello {props.name}</div>,
198+
title: `<title>${props.title}</title>`,
199+
metaTags: `<meta name="description" content="${props.description}" />`,
200+
};
201+
};
202+
HelmetComponent.renderFunction = true;
203+
ReactOnRails.register({ HelmetComponent });
204+
```
205+
206+
```erb
207+
<%# Ruby - MUST use react_component_hash %>
208+
<% helmet_data = react_component_hash("HelmetComponent",
209+
props: { name: "John", title: "My Page", description: "Page description" }) %>
210+
211+
<% content_for :head do %>
212+
<%= helmet_data["title"] %>
213+
<%= helmet_data["metaTags"] %>
214+
<% end %>
215+
216+
<div class="content">
217+
<%= helmet_data["componentHtml"] %>
218+
</div>
219+
```
220+
221+
### Return Type 4: Promise of String
222+
223+
```jsx
224+
// JavaScript
225+
const AsyncStringComponent = async (props) => {
226+
const data = await fetchData();
227+
return `<div>Hello ${data.name}</div>`;
228+
};
229+
AsyncStringComponent.renderFunction = true;
230+
ReactOnRails.register({ AsyncStringComponent });
231+
```
232+
233+
```erb
234+
<%# Ruby %>
235+
<%= react_component("AsyncStringComponent", props: { dataUrl: "/api/data" }) %>
236+
```
237+
238+
### Return Type 5: Promise of Object
239+
240+
```jsx
241+
// JavaScript
242+
const AsyncObjectComponent = async (props) => {
243+
const data = await fetchData();
244+
return {
245+
componentHtml: <div>Hello {data.name}</div>,
246+
title: `<title>${data.title}</title>`,
247+
metaTags: `<meta name="description" content="${data.description}" />`,
248+
};
249+
};
250+
AsyncObjectComponent.renderFunction = true;
251+
ReactOnRails.register({ AsyncObjectComponent });
252+
```
253+
254+
```erb
255+
<%# Ruby - MUST use react_component_hash %>
256+
<% helmet_data = react_component_hash("AsyncObjectComponent", props: { dataUrl: "/api/data" }) %>
257+
258+
<% content_for :head do %>
259+
<%= helmet_data["title"] %>
260+
<%= helmet_data["metaTags"] %>
261+
<% end %>
262+
263+
<div class="content">
264+
<%= helmet_data["componentHtml"] %>
265+
</div>
266+
```
267+
268+
### Return Type 6: Redirect Object
269+
270+
```jsx
271+
const RedirectComponent = (props, railsContext) => {
272+
if (!railsContext.currentUser) {
273+
return {
274+
redirectLocation: { pathname: '/login', search: '' }, // Object with pathname and search
275+
error: null,
276+
renderedHtml: null, // Use renderedHtml for consistency
277+
};
278+
}
279+
return {
280+
renderedHtml: <div>Welcome {railsContext.currentUser.name}</div>, // Use renderedHtml for consistency
281+
};
282+
};
283+
RedirectComponent.renderFunction = true;
284+
ReactOnRails.register({ RedirectComponent });
285+
```
286+
287+
```erb
288+
<%# Ruby - Either helper works %>
289+
<%= react_component("RedirectComponent") %>
290+
```
291+
292+
By understanding the different return types and which helper to use with each, you can create sophisticated server-rendered React components that fully integrate with your Rails views.

0 commit comments

Comments
 (0)