Skip to content

Commit 63c4cfe

Browse files
justin808claude
andauthored
docs: fix render function/helper API documentation (#3088)
## Summary Comprehensive documentation fixes for render functions, renderer functions, view helpers, and component registration APIs. Addresses documentation gaps and inaccuracies identified in #2911. ### Key changes: - **Legacy markers**: Mark `redirectLocation`/`routeError` as legacy (React Router v3/v4) with clear modern alternatives - **Compatibility matrix**: Add component type × Ruby helper compatibility table showing valid/invalid combinations - **`react_component_hash` behavior**: Document that it silently forces `prerender: true` - **`stream_react_component` behavior**: Document forced `prerender: true` and `immediate_hydration: true` - **`<Navigate>` SSR behavior**: Document that it's a no-op during SSR (renders null, redirect fires client-side only) - **Async + ExecJS**: Document that async render functions silently return `'{}'` with ExecJS - **Server render hash detection**: Document that `error` key triggers server-render-hash processing - **JSX deprecation**: Update outdated "v13 will throw error" reference - **`clientProps` merge**: Document symbol/string key handling and edge cases - **`registerServerComponent` gotcha**: Add prominent callout about different signatures per bundle (server takes object, client takes strings) - **`register()` JSDoc**: Rewrite with detection logic and complete return type documentation ### Files changed: | File | Changes | |---|---| | `docs/oss/core-concepts/render-functions.md` | Legacy markers, compatibility matrix, edge case docs | | `docs/oss/api-reference/view-helpers-api.md` | Compatibility matrix ref, prerender forcing, stream docs | | `docs/oss/api-reference/javascript-api.md` | Rewritten `register()` JSDoc | | `docs/oss/getting-started/using-react-on-rails.md` | Fixed example, legacy note | | `docs/oss/building-features/react-router.md` | `<Navigate>` SSR behavior section | | `docs/pro/react-server-components/inside-client-components.md` | `registerServerComponent` signature gotcha | Closes #2911 ## Test plan - [ ] Verify all markdown renders correctly on the docs site - [ ] Verify internal doc links resolve (pre-push hook validated 55 links with 0 errors) - [ ] Review compatibility matrix accuracy against source code 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Docs-only updates clarifying helper/render-function contracts; low risk aside from potential confusion if guidance is inaccurate or links regress. > > **Overview** > Updates OSS docs to **correct and expand render-function and helper API guidance**: rewrites `ReactOnRails.register()` JSDoc to explain component detection (param count / `.renderFunction`) and supported return shapes, and clarifies when render-functions vs renderer functions are valid. > > Strengthens helper documentation by explicitly documenting **forced behaviors and deprecations** (e.g., `react_component_hash` and `stream_react_component` forcing `prerender: true`, `immediate_hydration` ignored with warning), marking `redirectLocation`/`routeError` as *legacy* with modern alternatives, and adding a **compatibility matrix** mapping return types to `react_component`/`react_component_hash`/`stream_react_component`. > > Adds React Router SSR guidance for `<Navigate>` being a client-only redirect, fixes getting-started examples to match the `{ renderedHtml: { componentHtml, ... } }` shape, and updates Pro RSC docs to highlight `registerServerComponent`’s different server vs client signatures (with corrected client example). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 489d433. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Revised component registration API docs with a clear taxonomy of callable component types and accepted return shapes. * Clarified deprecation and legacy behavior for redirect/routeError, with migration guidance. * Updated react_component, react_component_hash, and stream_react_component docs: forced prerendering, compatibility matrix, immediate_hydration deprecation, and client-only renderer rules. * Documented React Router <Navigate> SSR behavior and recommended server-side redirects. * Clarified server/RSC registration signatures, client example simplification, clientProps hydration merge rules, and a hook/JSX return warning. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9791180 commit 63c4cfe

7 files changed

Lines changed: 117 additions & 30 deletions

File tree

docs/oss/api-reference/javascript-api.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,23 @@ The best source of docs is the `interface ReactOnRails` in [types/index.ts](http
2323
```js
2424
/**
2525
* Main entry point to using the react-on-rails NPM package. This is how Rails will be able to
26-
* find you components for rendering. Components get called with props, or you may use a
27-
* "Render-Function" to return a React component or an object with the following shape:
28-
* { renderedHtml, clientProps?, redirectLocation?, routeError? }.
29-
* `renderedHtml` may be a String, a React element, or a server-side hash.
30-
* For server rendering, if you wish to return multiple HTML strings from a Render-Function,
31-
* you may return an object from your Render-Function with a single top level property of
32-
* renderedHtml. Inside this object, place a key called componentHtml, along with any other
33-
* needed keys. This is useful when you using side effects libraries like React Helmet.
34-
* Your Ruby code with get this object as a Hash containing keys componentHtml and any other
35-
* custom keys that you added:
36-
* { renderedHtml: { componentHtml, customKey1, customKey2 } }
37-
* See the example in https://reactonrails.com/docs/building-features/react-helmet
26+
* find your components for rendering.
27+
*
28+
* Component detection: React on Rails distinguishes between component types by parameter count:
29+
* - 0-1 params: Regular React component (function or class)
30+
* - 2 params, or any function with `.renderFunction = true`: Render-Function — called with (props, railsContext),
31+
* returns a React component, `{ renderedHtml }` object, or Promise (Pro Node renderer only)
32+
* - 3 params: Renderer function — called with (props, railsContext, domNodeId),
33+
* responsible for calling ReactDOM.render/hydrate directly (client-only)
34+
*
35+
* Render-Functions can return:
36+
* - A React component (function or class) — used with `react_component`
37+
* - `{ renderedHtml: string }` — raw HTML string, used with `react_component`
38+
* - `{ renderedHtml: ReactElement }` — server rendering only, used with `react_component`
39+
* - `{ renderedHtml: { componentHtml, ...otherKeys } }` — used with `react_component_hash`
40+
* - `{ renderedHtml, clientProps }` — clientProps are merged into client hydration props
41+
* - `{ redirectLocation, routeError }` — legacy (React Router v3/v4), see render-functions docs
42+
*
3843
* @param components (key is component name, value is component)
3944
*/
4045
register(components);

docs/oss/api-reference/view-helpers-api.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Uncommonly used options:
2424
id: nil,
2525
```
2626

27-
- **component_name:** Can be a React component, created using a React Function Component, an ES6 class or a Render-Function that returns a React component (or, only on the server side, an object with shape `{ renderedHtml, clientProps?, redirectLocation?, routeError? }`), or a "renderer function" that manually renders a React component to the DOM (client-side only). Note, a "renderer function" is a special type of "Render-Function." A "renderer function" takes a 3rd param of a DOM ID.
27+
- **component_name:** Can be a React component, created using a React Function Component, an ES6 class or a Render-Function that returns a React component (or, only on the server side, an object with shape `{ renderedHtml, clientProps? }`), or a "renderer function" that manually renders a React component to the DOM (client-side only). Note, a "renderer function" is a special type of "Render-Function." A "renderer function" takes a 3rd param of a DOM ID. The legacy `redirectLocation` and `routeError` return fields are still supported but deprecated — see the [Render-Functions Guide](../core-concepts/render-functions.md#8-redirect-information-legacy) for modern alternatives.
2828
All options except `props, id, html_options` will inherit from your `react_on_rails.rb` initializer, as described [here](../configuration/README.md).
2929
- **general options:**
3030
- **props:** Ruby Hash which contains the properties to pass to the React object, or a JSON string. If you pass a string, we'll escape it for you.
@@ -51,10 +51,11 @@ Uncommonly used options:
5151
`react_component_hash` is used to return multiple HTML strings for server rendering, such as for
5252
adding meta-tags to a page. It is exactly like react_component except for the following:
5353

54-
1. `prerender: true` is automatically added to options, as this method doesn't make sense for
55-
client only rendering.
56-
2. Your JavaScript Render-Function for server rendering must return an Object rather than a React Component.
54+
1. **`prerender: true` is always forced** — even if you pass `prerender: false`, it is silently overwritten. This helper only makes sense for server rendering.
55+
2. Your JavaScript Render-Function for server rendering must return an Object with a `renderedHtml` property containing `componentHtml` and any other keys.
5756
3. Your view code must expect an object and not a string.
57+
4. Cannot be used with renderer functions (3-parameter functions) — these are client-only.
58+
5. The `immediate_hydration` option is no longer supported — if passed, it will be ignored with a deprecation warning.
5859

5960
Here is an example of ERB view code:
6061

@@ -91,6 +92,10 @@ You can call `rails_context` or `rails_context(server_side: true|false)` from yo
9192

9293
---
9394

95+
For a complete table of which component/return types work with each helper, see the [Compatibility Matrix](../core-concepts/render-functions.md#compatibility-matrix) in the Render-Functions guide.
96+
97+
---
98+
9499
### Renderer Functions (function that will call ReactDOM.render or ReactDOM.hydrate)
95100

96101
A "renderer function" is a Render-Function that accepts three arguments (rather than 2): `(props, railsContext, domNodeId) => { ... }`. Instead of returning a React component, a renderer is responsible for installing a callback that will call `ReactDOM.render` (in React 16+, `ReactDOM.hydrate`) to render a React component into the DOM. The "renderer function" is called at the same time the document ready event would instantiate the React components into the DOM.
@@ -155,6 +160,9 @@ Progressive server-side rendering using React 18+ streaming with `renderToPipeab
155160
- Progressive page loading with Suspense boundaries
156161
- Better perceived performance
157162

163+
> [!IMPORTANT]
164+
> `stream_react_component` always forces `prerender: true`, even if you pass a different value. The `immediate_hydration` option is no longer supported and will be ignored with a warning if passed. It only works with React components or render functions that return React components — it does not support render functions returning `{ renderedHtml }` objects.
165+
158166
See the [Streaming Server Rendering guide](../building-features/streaming-server-rendering.md) for usage details.
159167

160168
### rsc_payload_react_component

docs/oss/building-features/react-router.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,21 @@ For a practical example of route organization, see the [routes configuration fil
181181

182182
**Note:** This tutorial uses a legacy directory structure (`client/app/bundles`) from earlier React on Rails versions. Modern projects use `app/javascript/src/` structure as shown in this guide. The React Router integration patterns remain applicable.
183183

184+
## `<Navigate>` Component SSR Behavior
185+
186+
When using React Router's `<Navigate>` component with server-side rendering, be aware of the following behavior:
187+
188+
- **During SSR, `<Navigate>` is a no-op.** It renders `null` because `useEffect` does not fire on the server. No redirect is performed — Rails still sends the full response with whatever HTML was rendered.
189+
- **The redirect only fires on the client** via `useEffect`, which runs after hydration. This means:
190+
- Users briefly see the SSR content before being redirected (content flash).
191+
- Search engines see the original page content, not a redirect — there is no HTTP 301/302 status code.
192+
- The client-side redirect adds a navigation entry to the browser history.
193+
194+
**Recommendations for SSR redirects:**
195+
196+
1. **Prefer Rails controller redirects** for auth guards, canonical URLs, and SEO-critical redirects. Check conditions in your controller and call `redirect_to` before rendering.
197+
2. **Use `<Navigate>` only for client-side routing transitions** where a brief flash is acceptable and SEO redirect semantics are not needed.
198+
184199
## Additional Resources
185200

186201
- [React Router Official Documentation](https://reactrouter.com/)

docs/oss/core-concepts/render-functions.md

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,24 @@ const MyComponent = async (props, _railsContext) => {
121121
};
122122
```
123123

124-
### 8. Redirect Information
124+
### 8. Redirect Information (Legacy)
125125

126126
> [!NOTE]
127-
> React on Rails does not perform actual page redirections. Instead, it returns an empty component and relies on the front end to handle the redirection when the router is rendered. The `redirectLocation` property is logged in the console and ignored by the server renderer. If the `routeError` property is not null or undefined, it is logged and will cause Ruby to throw a `ReactOnRails::PrerenderError` if the `raise_on_prerender_error` configuration is enabled.
127+
> **`redirectLocation` and `routeError` have significant limitations.** These fields originated from React Router v3/v4 integrations but are still supported at the runtime level. Be aware of:
128+
>
129+
> - `redirectLocation` does **not** trigger an actual server-side redirect — Rails still returns the same response with an empty `<div>`. The redirect only takes effect once the client-side router renders.
130+
> - `routeError` only triggers `raise_on_prerender_error` behavior (if enabled) — it does not produce a user-facing error page.
131+
> - Modern React Router v6 Declarative Mode (`StaticRouter`) has no mechanism to produce these values.
132+
> - React Router v6 Data Mode (`createStaticHandler`) handles redirects via `Response` objects, not these fields.
133+
>
134+
> **Modern alternatives:**
135+
>
136+
> - For redirects during SSR, handle them in your Rails controller (e.g., check auth before rendering).
137+
> - For client-side redirects, use `<Navigate to="/path" />` (note: this is a [no-op during SSR](../building-features/react-router.md#navigate-component-ssr-behavior)).
138+
> - For route errors, use React Router's `errorElement` or an `ErrorBoundary`.
128139
129140
```jsx
141+
// Legacy pattern — prefer modern alternatives above
130142
const MyComponent = (props, _railsContext) => {
131143
return {
132144
redirectLocation: { pathname: '/new-path', search: '' },
@@ -143,12 +155,17 @@ Take a look at [serverRenderReactComponent.test.ts](https://github.com/shakacode
143155

144156
2. **Objects Require Specific Properties** - Non-promise objects must include a `renderedHtml` property to be valid when used with `react_component`.
145157

146-
3. **Async Functions Support Server Render Hashes** - When using the React on Rails Pro Node renderer, async render-functions can return React components, strings, or full server render hashes, including `clientProps`, `redirectLocation`, and `routeError`. See [8. Redirect Information](#8-redirect-information).
158+
3. **Async Functions Require the Pro Node Renderer** - Async render-functions (returning Promises) only work with the React on Rails Pro Node renderer. With ExecJS, an async render function silently returns `'{}'` with only a `console.error` — the empty result can be confusing to debug.
147159

148160
4. **`clientProps` are merged back into hydration props** - If a server render result includes `clientProps`, React on Rails merges those keys into the client hydration props generated by `react_component`.
149161
- Use this to pass server-only computed hydration data (for example router dehydrated state).
150162
- Merge order is `original_props.merge(clientProps)`, so keys from `clientProps` override matching original keys.
151163
- This merge requires your original `props:` to be a Ruby `Hash` or a JSON string representing an object.
164+
- The merge handles symbol vs. string key coexistence: if `clientProps` contains a string key `"foo"` and the original props contain a symbol key `:foo`, the merge updates the existing symbol key rather than creating a duplicate. An error is raised if both string and symbol versions of the same key exist in the original props.
165+
166+
5. **Server render result detection is broad** - React on Rails treats any object containing a `renderedHtml`, `redirectLocation`, `routeError`, or `error` key as a server render result (not a React component). This means returning an object with an `error` key from a render function triggers server-render-hash processing rather than being treated as a React component. Note that `error` is only used for detection — it is not itself treated as an exception descriptor. Only `routeError` sets the error flag during processing. This may be surprising if you intended `error` as a regular data field.
167+
168+
6. **Don't return JSX directly from render functions** - Render functions should return a React component (function or class), not a React element (JSX). Returning JSX directly causes React Hooks to break silently. A deprecation warning is logged but the code currently still works for backward compatibility.
152169

153170
## Ruby Helper Functions
154171

@@ -179,6 +196,9 @@ If your render-function returns `clientProps`, this helper also injects those va
179196
180197
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.
181198
199+
> [!IMPORTANT]
200+
> `react_component_hash` **always forces `prerender: true`**, even if you explicitly pass `prerender: false`. This is because the helper only makes sense for server rendering — it needs the server to produce the hash of HTML strings. Passing `prerender: false` will be silently overwritten.
201+
182202
```ruby
183203
# With a render function that returns an object with multiple HTML properties
184204
<% helmet_data = react_component_hash("HelmetComponent", props: {
@@ -208,13 +228,38 @@ This helper accepts render-functions that return objects with a `renderedHtml` p
208228
#### Not suitable for
209229

210230
- Simple component rendering
211-
- Client-side only rendering (always uses server rendering)
231+
- Client-side only rendering (always forces server rendering)
232+
- Renderer functions (3-parameter functions) — these are client-only
212233

213234
#### Requirements
214235

215-
- The render function MUST return an object
216-
- The object MUST include a `componentHtml` key
217-
- All other keys are optional and can be accessed in your Rails view
236+
- The render function MUST return an object with a `renderedHtml` property
237+
- The `renderedHtml` object MUST include a `componentHtml` key
238+
- All other keys in the `renderedHtml` object are optional and can be accessed in your Rails view
239+
- Cannot be used with renderer functions (3-parameter functions that call `ReactDOM.render` directly)
240+
241+
## Compatibility Matrix
242+
243+
This table shows which component/return types are valid with each Ruby helper:
244+
245+
| Return Type | `react_component` | `react_component_hash` | `stream_react_component` (Pro) |
246+
| ------------------------------------------------------------ | ---------------------- | ----------------------------------------------------------------- | ----------------------------------------------------- |
247+
| React component (function/class) ||||
248+
| Render function → React component ||||
249+
| Render function → `{ renderedHtml: string }` ||||
250+
| Render function → `{ renderedHtml: ReactElement }` | ✅ (prerender only) |||
251+
| Render function → `{ renderedHtml: { componentHtml, ... } }` ||||
252+
| Render function → Promise (compatible return shape) | ✅ (Pro Node renderer) | ✅ (Pro Node renderer, must resolve to hash with `componentHtml`) | ✅ (Pro Node renderer, must resolve to React element) |
253+
| Renderer function (3 params) | ✅ (client-only) |||
254+
255+
> **Note:** ❌ marks an unsupported combination. Some mismatches raise a `ReactOnRails::Error` directly in Ruby (e.g., `react_component` with a hash result raises `"Use react_component_hash (not react_component)..."`, and `react_component_hash` with a non-hash result raises `"Render-Function... expected to return an Object"`). Others (renderer functions used in server rendering, Pro Node renderer mismatches) throw a JavaScript error that becomes `ReactOnRails::PrerenderError` when `raise_on_prerender_error` is enabled, or is embedded as an error comment in the HTML when it is not. Either way, these combinations are not silent data failures — they will surface as errors or visible error output.
256+
257+
**Key constraints:**
258+
259+
- **`react_component_hash`** always forces `prerender: true` — it cannot be used for client-only rendering.
260+
- **`stream_react_component`** (Pro) always forces `prerender: true`. The `immediate_hydration` option is no longer supported and will be ignored with a warning if passed.
261+
- **Renderer functions** (3 parameters) are client-only — they call `ReactDOM.render`/`hydrate` directly, so server rendering with them throws an error.
262+
- **Async render functions** (returning Promises) require the Pro Node renderer. With ExecJS, they silently return `'{}'`.
218263

219264
## Examples with Appropriate Helper Methods
220265

@@ -358,9 +403,13 @@ ReactOnRails.register({ AsyncReactComponent });
358403
<%= react_component("AsyncReactComponent", props: { dataUrl: "/api/data" }) %>
359404
```
360405

361-
### Return Type 8: Redirect Object
406+
### Return Type 8: Redirect Object (Legacy)
407+
408+
> [!WARNING]
409+
> This is a legacy pattern from React Router v3/v4. See [8. Redirect Information (Legacy)](#8-redirect-information-legacy) for modern alternatives. The `redirectLocation` does not trigger an actual server-side redirect — Rails returns an empty `<div>` and the redirect only fires on the client.
362410

363411
```jsx
412+
// Legacy pattern — prefer handling redirects in your Rails controller
364413
const RedirectComponent = (props, railsContext) => {
365414
if (!railsContext.currentUser) {
366415
return {

docs/oss/getting-started/using-react-on-rails.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,13 @@ For advanced server rendering (like routing + hydration state), you can return a
192192
({
193193
renderedHtml: {
194194
componentHtml,
195-
redirectLocation,
196-
error,
195+
title,
196+
metaTags,
197197
},
198198
});
199199
```
200200

201-
Use with `react_component_hash` helper for multiple HTML strings (useful with React Helmet for meta tags).
201+
Use with `react_component_hash` helper for multiple HTML strings (useful with React Helmet for meta tags). Note that `react_component_hash` always forces `prerender: true`.
202202

203203
If you also need to send extra data back to the browser for hydration, add `clientProps`:
204204

@@ -209,13 +209,14 @@ If you also need to send extra data back to the browser for hydration, add `clie
209209
// Optional: merged into client hydration props on the Rails side
210210
routerDehydratedState: { url: railsContext.location },
211211
},
212-
redirectLocation: { pathname: '/login', search: '' }, // Optional
213-
routeError: null, // Optional
214212
});
215213
```
216214

217215
Think of it like this: `renderedHtml` is what the user sees now, and `clientProps` is a lunchbox of data React uses after the page loads.
218216

217+
> [!NOTE]
218+
> The legacy `redirectLocation` and `routeError` return fields (from React Router v3/v4 era) are still supported but deprecated. They do not trigger actual server-side redirects or error pages. See the [Render-Functions Guide](../core-concepts/render-functions.md#8-redirect-information-legacy) for modern alternatives.
219+
219220
For complete Render-Function details and examples, see the [Render-Functions Guide](../core-concepts/render-functions.md).
220221

221222
---

0 commit comments

Comments
 (0)