docs/oss/api-reference/view-helpers-api.md
Once the bundled files have been generated in your app/assets/webpack folder, and you have registered your components, you will want to render these components on your Rails views using the included helper method, react_component.
react_component(component_name,
props: {},
prerender: nil,
hydrate_on: :immediate,
html_options: {})
Uncommonly used options:
trace: nil,
replay_console: nil,
raise_on_prerender_error: nil,
id: nil,
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.
If your render function returns a hash with multiple HTML strings (e.g.,
{ renderedHtml: { componentHtml, title, metaTags } }),react_componentraises aReactOnRails::Errortelling you to usereact_component_hashinstead.react_componentis for rendering a single HTML result;react_component_hashis for rendering multiple HTML strings to place in different parts of the page.
All options except props, id, html_options will inherit from your react_on_rails.rb initializer, as described in the configuration documentation.
general options:
REACT_ON_RAILS_PRERENDER_OVERRIDE=true|false to force prerendering on or off globally.
Precedence is: REACT_ON_RAILS_PRERENDER_OVERRIDE > component option (prerender:) > initializer default (config.prerender).append_javascript_pack_tag and append_stylesheet_pack_tag under the hood.:immediate (default), :visible, and :idle. See Hydration Scheduling. :interaction is not supported. Deferred modes are OSS-only; React on Rails Pro currently accepts only :immediate.display:inline-block. You may also use an option of tag: "span" to replace the use of the default DIV tag to be a SPAN tag.railsContext and your props.options if prerender (server rendering) is true:
logging_on_server set to true, you'll still see the errors on the server.Rails.env.development? (true in development, false in production). True will throw an error on server-side rendering. Your controller will have to handle the error.clientProps merge behavior: If a prerender result includes clientProps, React on Rails merges them into the generated client hydration props payload (props.merge(clientProps)). The original props: value must be a Ruby Hash or a JSON string representing an object.<%= react_on_rails_preload_links("HelloWorld", "comments_list") %>
Use react_on_rails_preload_links in a layout or view <head> when you know which auto-bundled React components the page will render. The helper resolves each component to its generated Shakapacker pack (generated/ComponentName) and emits preload link tags for the manifest assets. Pass component names as PascalCase, camelCase, or snake_case strings; hyphenated names are rejected because they cannot be normalized reliably.
For JavaScript chunks, plain script assets render as <link rel="preload" as="script">. Module assets render as <link rel="modulepreload"> when the manifest marks the asset as a module or the emitted file has an .mjs extension. CSS chunks render as <link rel="preload" as="style">. Component packs without CSS assets simply skip the stylesheet preload.
<head>
<%= react_on_rails_preload_links("ProductPage") %>
<%= stylesheet_pack_tag "application" %>
</head>
<body>
<%= react_component("ProductPage", props: @product_props, auto_load_bundle: true) %>
<%= javascript_pack_tag "application" %>
</body>
Because preload hints belong in <head>, pass component names that are known before the component renders.
This helper only emits HTML link tags. Keep the normal stylesheet_pack_tag and javascript_pack_tag calls in the layout so the browser still applies and executes the assets.
When using a CDN asset host, keep Shakapacker's Subresource Integrity and crossorigin settings consistent between preload tags and the final script/style tags so the browser can reuse the preloaded response.
With Shakapacker versions before 8.4 that do not expose config.integrity, preload links are still emitted without integrity attributes. React on Rails only adds preload SRI attributes when Shakapacker exposes integrity settings and marks them enabled.
React 19 Alternative: For metadata use cases (page titles, meta tags, canonical URLs), consider using React 19 Native Metadata with
react_componentorstream_react_componentinstead. React 19 natively hoists<title>,<meta>, and<link>tags to<head>, eliminating the need for a render-function andreact_component_hash. See the migration guide for step-by-step instructions.
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:
prerender: true is forced, not defaulted. This helper always prerenders on the server — passing prerender: false has no effect. Client-only rendering is incompatible with the "return multiple HTML strings" use case, so the option cannot be disabled.{ renderedHtml: { componentHtml, ...otherKeys } }, where:
componentHtml is mandatory. Missing it raises ReactOnRails::Error with a message pointing to this requirement. This key contains the main server-rendered HTML that gets placed where the helper is called.html_safe strings, ready to be inserted anywhere in the layout (meta tags in <head>, sidebars, etc.).react_component_hash_result["componentHtml"], ["title"], etc.Here is an example of ERB 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"] %>
And here is the JavaScript code:
export default (props, _railsContext) => {
const componentHtml = renderToString(<ReactHelmet {...props} />);
const helmet = Helmet.renderStatic();
const renderedHtml = {
componentHtml,
title: helmet.title.toString(),
};
return { renderedHtml };
};
You can call rails_context or rails_context(server_side: true|false) from your controller or view to see what values are in the Rails Context. Pass true or false depending on whether you want to see the server-side or the client-side rails_context. Typically, for computing cache keys, you should leave server_side as the default true. When calling this from a controller method, use helpers.rails_context.
A renderer function is a Render-Function that declares three parameters: (props, railsContext, domNodeId) => { ... }. React on Rails detects renderer functions purely by parameter count (Function.length === 3) — the names don't matter. Instead of returning a React component, a renderer function is responsible for mounting the React tree itself by calling ReactDOM.hydrateRoot (for SSR'd HTML) or ReactDOM.createRoot(...).render(...) (for empty containers) against the DOM node identified by domNodeId. The renderer function is invoked at the point where React on Rails would normally mount the component automatically.
Why would you want to take over mounting yourself? One use case is code splitting: you may want to defer mounting a component until its code chunk has loaded, or until the container scrolls into view, instead of mounting it eagerly on page load. For modern code splitting with server-side rendering, see the React on Rails Pro loadable-components guide.
[!IMPORTANT] Renderer functions are strictly client-only. There is no DOM on the server, so a renderer function cannot produce SSR output. React on Rails detects renderer functions at registration time and will throw a descriptive error like
Detected a renderer while server rendering component 'X'. See https://reactonrails.com/docs/core-concepts/render-functions for more information.if you attempt to use one withreact_component(... prerender: true),react_component_hash(which forces prerendering), orstream_react_component(which is server-streaming only). For rendering that needs to run on the server, use a regular render function instead.
Because a renderer function owns the React root it creates, React on Rails cannot unmount that root for you the way it does for the components it mounts itself. With Turbo or Turbolinks, the page swaps without a full reload, so a renderer that never unmounts leaks its root (and any subscriptions or timers it holds) on every navigation.
To opt in to cleanup, return a teardown wrapper — { teardown: () => void | Promise<void> }, or a promise resolving to one — from the renderer. React on Rails stores it and runs it when the mount is torn down: on Turbo/Turbolinks navigation (when the framework swaps in the next page) or when the same domNodeId node is replaced. Returning nothing keeps the previous (leaky) behavior, so existing renderers are unaffected.
import ReactDOMClient from 'react-dom/client';
// Renderer function: 3 params, mounts itself, returns a teardown wrapper.
const MyRenderer = (props, _railsContext, domNodeId) => {
const domNode = document.getElementById(domNodeId);
if (!domNode) {
throw new Error(`Missing DOM element with id: ${domNodeId}`);
}
// This example always creates a fresh root. See the hydration note below if your renderer
// needs to hydrate server-rendered markup.
const root = ReactDOMClient.createRoot(domNode);
root.render(<MyComponent {...props} />);
// Unmounted automatically on the next Turbo navigation (or same-id node replacement).
return { teardown: () => root.unmount() };
};
[!NOTE] Hydrating server-rendered markup?
prerenderis not a prop React on Rails injects — the top-levelprerender:render option only controls server rendering and is rejected for renderer functions (see the note above). If your client renderer also serves components that were rendered on the server through a separate server bundle (a server/client split), pass an application-level signal in the component'sprops, such asserverRendered, and branch on it. The in-repo dummy apps use their own fixture props for this decision; the custom flag here is just an example for renderers that need an explicit hydrate-vs-render signal. Remove that renderer-only flag before spreading props into your component:const { serverRendered, ...componentProps } = props;, then callReactDOMClient.hydrateRoot(domNode, <MyComponent {...componentProps} />)whenserverRenderedis true.
Under the React 16/17 legacy API there is no root handle, so unmount by container node instead:
import ReactDOM from 'react-dom';
const MyLegacyRenderer = (props, _railsContext, domNodeId) => {
const { serverRendered, ...componentProps } = props;
const domNode = document.getElementById(domNodeId);
if (!domNode) {
throw new Error(`Missing DOM element with id: ${domNodeId}`);
}
if (serverRendered) {
ReactDOM.hydrate(<MyComponent {...componentProps} />, domNode);
} else {
ReactDOM.render(<MyComponent {...componentProps} />, domNode);
}
return { teardown: () => ReactDOM.unmountComponentAtNode(domNode) };
};
[!NOTE] Synchronous teardowns are always honored. An async teardown is best-effort in the open-source package: if a navigation or node replacement happens before the renderer resolves its teardown, that still-pending teardown may be dropped. React on Rails logs a
console.errorwhen this happens — search forresolved after its mount was removed(the teardown was dropped) orError resolving renderer teardown(the render promise rejected) — so the dropped teardown is diagnosable rather than silent. React on Rails Pro's client renderer awaits the renderer and handles this race reliably.
React Router is supported via manual integration, including server-side rendering. See:
TanStack Router has a first-class SSR helper through react-on-rails-pro/tanstack-router (requires React on Rails Pro). See TanStack Router guide.
server_render_js(js_expression, options = {})
replay_console (boolean)This is a helper method that takes any JavaScript expression and returns the output from evaluating it. If you have more than one line that needs to be executed, wrap it in an IIFE. JS exceptions will be caught and console messages handled properly.
The following view helpers are available exclusively with React on Rails Pro. Install the Pro gem to use them. ShakaCode Trust-Based Commercial Licensing lets you evaluate Pro without a token in development, test, CI/CD, and staging; production deployments require a paid license from Pro pricing and sign up.
Fragment caching helpers that cache React component rendering to improve performance. The API is the same as react_component and react_component_hash, but with these differences:
cache_key takes the same parameters as any Rails cache view helper.Example usage:
<%= cached_react_component("App", cache_key: [@user, @post], prerender: true) do
some_slow_method_that_returns_props
end %>
Progressive server-side rendering using React 18+ streaming with renderToPipeableStream. This enables:
See the Streaming Server Rendering guide for usage details.
[!IMPORTANT]
stream_react_componentalways forcesprerender: true— passingprerender: falsehas no effect. It only supports React components and render functions that return React components; render functions returning a{ renderedHtml }hash are incompatible (see compatibility matrix).
Async-props variant of stream_react_component. Use it when Rails has synchronous props plus other props that should stream later through Suspense boundaries.
Use this helper for RSC Server Components with config.enable_rsc_support = true. For non-RSC streaming SSR, use stream_react_component.
It accepts the same options as stream_react_component, plus a block that receives an emitter. Call emit.call(prop_name, value) for each async prop:
<%= stream_react_component_with_async_props("ProductPage",
props: { name: @product.name, price: @product.price }) do |emit|
emit.call("reviews", @product.reviews.as_json(only: [:id, :text, :rating]))
end %>
RSC Server Components rendered this way receive getReactOnRailsAsyncProp, which returns a Promise for each emitted prop.
For the complete React component pattern using WithAsyncProps and getReactOnRailsAsyncProp, see Data Fetching in React on Rails Pro.
[!IMPORTANT]
stream_react_component_with_async_propsrequiresconfig.enable_rsc_support = trueand always forcesprerender: true— passingprerender: falsehas no effect. It requires the same controller setup asstream_react_component: the controller must callstream_view_containing_react_components. Likestream_react_component, it only supports React components and render functions that return React components; render functions returning a{ renderedHtml }hash are incompatible (see compatibility matrix).The emitter block runs normal Ruby code sequentially, so
emit.calldoes not parallelize slow queries by itself. For independent slow data sources, start the work concurrently before emitting values; see Avoiding Server-Side Waterfalls.
Renders React Server Component (RSC) payloads in NDJSON format for client-side consumption. Used in conjunction with RSC support to enable:
The mounted rsc_payload_route normally calls this helper for you. Call it directly only for custom RSC payload rendering.
See the React on Rails Pro Configuration for RSC setup.
Async-props variant of rsc_payload_react_component. Use it only when custom RSC payload rendering needs Rails-emitted async props, such as an overridden payload route or template. For standard streamed ERB views, use stream_react_component_with_async_props.
Requires enable_rsc_support = true in configuration, same as rsc_payload_react_component — see React on Rails Pro Configuration.
It accepts the same options as rsc_payload_react_component, plus a block that receives an emitter:
<%= rsc_payload_react_component_with_async_props("ProductPage",
props: { name: @product.name, price: @product.price }) do |emit|
emit.call("reviews", @product.reviews.as_json(only: [:id, :text, :rating]))
end %>
[!IMPORTANT]
rsc_payload_react_component_with_async_propsrequiresconfig.enable_rsc_support = trueand always forcesprerender: true— passingprerender: falsehas no effect. Use this helper only for custom RSC payload rendering; standard streamed ERB views should usestream_react_component_with_async_props.The emitter block runs normal Ruby code sequentially, so
emit.calldoes not parallelize slow queries by itself. For independent slow data sources, start the work concurrently before emitting values; see Avoiding Server-Side Waterfalls.
See the lib/react_on_rails/helper.rb source.