Module: ReactOnRailsProHelper

Includes:
ScoutApm::Tracer
Defined in:
app/helpers/react_on_rails_pro_helper.rb

Overview

rubocop:disable Metrics/ModuleLength

Constant Summary collapse

STATIC_RSC_RENDER_DIAGNOSTIC_EVENT =
"render_static_rsc_component.react_on_rails_pro"
HTML_SPACE_CHARACTERS =
[" ", "\t", "\n", "\f", "\r"].freeze
HTML_QUOTE_CHARACTERS =
['"', "'"].freeze
SCRIPT_OPEN_TAG =
"<script"
SCRIPT_OPEN_TAG_LENGTH =
7
SCRIPT_CLOSE_TAG =
"</script"
SCRIPT_CLOSE_TAG_LENGTH =
8
STATIC_RSC_PAYLOAD_SCRIPT_MARKER_ATTRIBUTE =
"data-react-on-rails-rsc-payload"
STATIC_RSC_ASSET_DIAGNOSTIC_CACHE_MUTEX =
Mutex.new

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.static_rsc_asset_diagnostic_cacheObject (readonly)

Returns the value of attribute static_rsc_asset_diagnostic_cache.



40
41
42
# File 'app/helpers/react_on_rails_pro_helper.rb', line 40

def static_rsc_asset_diagnostic_cache
  @static_rsc_asset_diagnostic_cache
end

Class Method Details

.clear_static_rsc_asset_diagnostic_cache!Object



42
43
44
45
46
# File 'app/helpers/react_on_rails_pro_helper.rb', line 42

def clear_static_rsc_asset_diagnostic_cache!
  STATIC_RSC_ASSET_DIAGNOSTIC_CACHE_MUTEX.synchronize do
    @static_rsc_asset_diagnostic_cache = {}
  end
end

Instance Method Details

#async_react_component(component_name, options = {}) ⇒ ReactOnRailsPro::AsyncValue

Renders a React component asynchronously, returning an AsyncValue immediately. Multiple async_react_component calls will execute their HTTP rendering requests concurrently instead of sequentially.

Requires the controller to include ReactOnRailsPro::AsyncRendering and call enable_async_react_rendering.

Examples:

<% header = async_react_component("Header", props: @header_props) %>
<% sidebar = async_react_component("Sidebar", props: @sidebar_props) %>
<%= header.value %>
<%= sidebar.value %>

Parameters:

  • component_name (String)

    Name of your registered component

  • options (Hash) (defaults to: {})

    Same options as react_component

Returns:



389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'app/helpers/react_on_rails_pro_helper.rb', line 389

def async_react_component(component_name, options = {})
  unless defined?(@react_on_rails_async_barrier) && @react_on_rails_async_barrier
    raise ReactOnRailsPro::Error,
          "async_react_component requires AsyncRendering concern. " \
          "Include ReactOnRailsPro::AsyncRendering in your controller and call enable_async_react_rendering."
  end

  task = @react_on_rails_async_barrier.async do
    react_component(component_name, options)
  end

  ReactOnRailsPro::AsyncValue.new(task:)
end

#buffered_stream_react_component(component_name, options = {}) ⇒ Object

Renders a stream-capable component through the streaming/RSC renderer, but buffers every chunk before returning HTML to Rails. Use this for static/cacheable responses that need RSC rendering without ActionController::Live committing headers on the first streamed byte.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'app/helpers/react_on_rails_pro_helper.rb', line 177

def buffered_stream_react_component(component_name, options = {})
  options = options.dup
  options[:prerender] = true
  if options.key?(:immediate_hydration)
    ReactOnRails::Helper.warn_removed_immediate_hydration_option("buffered_stream_react_component")
    options.delete(:immediate_hydration)
  end

  on_complete = options.delete(:on_complete)
  collect_chunks = on_complete.respond_to?(:call)
  buffer = collect_chunks ? [] : +""

  internal_stream_react_component(component_name, options).each_chunk do |chunk|
    buffer << chunk.to_s
  end

  if collect_chunks
    html = buffer.join.html_safe
    on_complete.call(buffer)
    html
  else
    buffer.html_safe
  end
end

#cached_async_react_component(component_name, raw_options = {}) { ... } ⇒ ReactOnRailsPro::AsyncValue, ReactOnRailsPro::ImmediateAsyncValue

Renders a React component asynchronously with caching support. Cache lookup is synchronous - cache hits return immediately without async. Cache misses trigger async render and cache the result on completion.

All the same options as cached_react_component apply:

  1. You must pass the props as a block (evaluated only on cache miss)
  2. Provide the cache_key option
  3. Optionally provide :cache_options for Rails.cache (expires_in, etc.)
  4. Provide :if or :unless for conditional caching
  5. Optionally provide :cache_tags for revalidation via ReactOnRailsPro.revalidate_tag

Examples:

<% card = cached_async_react_component("ProductCard", cache_key: @product) { @product.to_props } %>
<%= card.value %>

Parameters:

  • component_name (String)

    Name of your registered component

  • options (Hash)

    Options including cache_key and cache_options

Yields:

  • Block that returns props (evaluated only on cache miss)

Returns:



423
424
425
426
427
428
# File 'app/helpers/react_on_rails_pro_helper.rb', line 423

def cached_async_react_component(component_name, raw_options = {}, &block)
  ReactOnRailsPro::Utils.with_trace(component_name) do
    check_caching_options!(raw_options, block)
    fetch_async_react_component(component_name, raw_options, &block)
  end
end

#cached_buffered_stream_react_component(component_name, raw_options = {}, &block) ⇒ Object

Cached version of buffered_stream_react_component. Unlike cached_stream_react_component, this returns the complete HTML string from the cache/miss path and does not require stream_view_containing_react_components. The on_complete callback is unsupported because cache hits do not replay chunks.



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'app/helpers/react_on_rails_pro_helper.rb', line 315

def cached_buffered_stream_react_component(component_name, raw_options = {}, &block)
  ReactOnRailsPro::Utils.with_trace(component_name) do
    check_caching_options!(raw_options, block)
    if raw_options[:on_complete].respond_to?(:call)
      raise ReactOnRailsPro::Error,
            "cached_buffered_stream_react_component does not support on_complete; " \
            "use buffered_stream_react_component for chunk callbacks"
    end

    render_options = options_with_auto_load_bundle(raw_options)
    cache_options = render_options.merge(
      cache_key: lambda do
        raw_cache_key = raw_options[:cache_key]
        cache_key_value = raw_cache_key.respond_to?(:call) ? raw_cache_key.call : raw_cache_key

        ["buffered_stream_react_component", cache_key_value]
      end,
      prerender: true
    )

    cached_result = fetch_react_component(component_name, cache_options) do
      options = render_options.merge(
        props: yield,
        skip_prerender_cache: true
      )
      buffered_stream_react_component(component_name, options)
    end
    cached_result.html_safe
  end
end

#cached_react_component(component_name, raw_options = {}, &block) ⇒ Object

Provide caching support for react_component in a manner akin to Rails fragment caching. All the same options as react_component apply with the following difference:

  1. You must pass the props as a block. This is so that the evaluation of the props is not done if the cache can be used.
  2. Provide the cache_key option cache_key: String or Array (or Proc returning a String or Array) containing your cache keys. If prerender is set to true, the server bundle digest will be included in the cache key. When RSC support is enabled and the RSC bundle exists, the RSC bundle digest is also included. The cache_key value is the same as used for conventional Rails fragment caching.
  3. Optionally provide the :cache_options key with a value of a hash including as :compress, :expires_in, :race_condition_ttl as documented in the Rails Guides
  4. Provide boolean values for :if or :unless to conditionally use caching.
  5. Optionally provide the :cache_tags option: String or Array (or Proc, or any object responding to cache_key, such as an ActiveRecord model) of revalidation tags. Tagged cache entries can be deleted later with ReactOnRailsPro.revalidate_tag(tag). Tag revalidation is best-effort, so also set cache_options: { expires_in: ... } to bound staleness.


79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'app/helpers/react_on_rails_pro_helper.rb', line 79

def cached_react_component(component_name, raw_options = {}, &block)
  ReactOnRailsPro::Utils.with_trace(component_name) do
    check_caching_options!(raw_options, block)
    cache_options = options_with_auto_load_bundle(raw_options)

    fetch_react_component(component_name, cache_options) do
      sanitized_options = cache_options.dup
      sanitized_options[:props] = yield
      sanitized_options[:skip_prerender_cache] = true
      react_component(component_name, sanitized_options)
    end
  end
end

#cached_react_component_hash(component_name, raw_options = {}, &block) ⇒ Object

Provide caching support for react_component_hash in a manner akin to Rails fragment caching. All the same options as react_component_hash apply with the following difference:

  1. You must pass the props as a block. This is so that the evaluation of the props is not done if the cache can be used.
  2. Provide the cache_key option cache_key: String or Array (or Proc returning a String or Array) containing your cache keys. Since prerender is automatically set to true, the server bundle digest will be included in the cache key. When RSC support is enabled and the RSC bundle exists, the RSC bundle digest is also included. The cache_key value is the same as used for conventional Rails fragment caching.
  3. Optionally provide the :cache_options key with a value of a hash including as :compress, :expires_in, :race_condition_ttl as documented in the Rails Guides
  4. Provide boolean values for :if or :unless to conditionally use caching.
  5. Optionally provide the :cache_tags option: String or Array (or Proc, or any object responding to cache_key, such as an ActiveRecord model) of revalidation tags. Tagged cache entries can be deleted later with ReactOnRailsPro.revalidate_tag(tag). Tag revalidation is best-effort, so also set cache_options: { expires_in: ... } to bound staleness.


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'app/helpers/react_on_rails_pro_helper.rb', line 110

def cached_react_component_hash(component_name, raw_options = {}, &block)
  raw_options[:prerender] = true

  ReactOnRailsPro::Utils.with_trace(component_name) do
    check_caching_options!(raw_options, block)
    cache_options = options_with_auto_load_bundle(raw_options)

    fetch_react_component(component_name, cache_options) do
      sanitized_options = cache_options.dup
      sanitized_options[:props] = yield
      sanitized_options[:skip_prerender_cache] = true
      react_component_hash(component_name, sanitized_options)
    end
  end
end

#cached_static_rsc_component(component_name, raw_options = {}, &block) ⇒ Object

Cached static RSC rendering for public pages that use a sidecar pack instead of hydrating the generated page pack. The cached value is the buffered HTML after removing embedded RSC payload bootstrap scripts.



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'app/helpers/react_on_rails_pro_helper.rb', line 349

def cached_static_rsc_component(component_name, raw_options = {}, &block)
  ReactOnRailsPro::Utils.with_trace(component_name) do
    raw_options = raw_options.dup
    diagnostics_context = static_rsc_diagnostics_context(raw_options)

    check_caching_options!(raw_options, block)
    check_cached_static_rsc_options!(raw_options)

    render_options = options_with_auto_load_bundle(raw_options)
    cache_options = static_rsc_cache_options(raw_options, render_options)

    cached_result = render_cached_static_rsc_component(
      component_name,
      cache_options,
      render_options,
      diagnostics_context,
      &block
    )
    emit_static_rsc_render_diagnostics(component_name, render_options, diagnostics_context, cached_result)
    cached_result.html_safe
  end
end

#cached_stream_react_component(component_name, raw_options = {}, &block) ⇒ Object

Provide caching support for stream_react_component in a manner akin to Rails fragment caching. All the same options as stream_react_component apply with the following differences:

  1. You must pass the props as a block. This is so that the evaluation of the props is not done if the cache can be used.
  2. Provide the cache_key option cache_key: String or Array (or Proc returning a String or Array) containing your cache keys. Since prerender is automatically set to true, the server bundle digest will be included in the cache key. When RSC support is enabled and the RSC bundle exists, the RSC bundle digest is also included. The cache_key value is the same as used for conventional Rails fragment caching.
  3. Optionally provide the :cache_options key with a value of a hash including as :compress, :expires_in, :race_condition_ttl as documented in the Rails Guides
  4. Provide boolean values for :if or :unless to conditionally use caching.
  5. Optionally provide the :cache_tags option: String or Array (or Proc, or any object responding to cache_key, such as an ActiveRecord model) of revalidation tags. Tagged cache entries can be deleted later with ReactOnRailsPro.revalidate_tag(tag). Tag revalidation is best-effort, so also set cache_options: { expires_in: ... } to bound staleness.


304
305
306
307
308
309
# File 'app/helpers/react_on_rails_pro_helper.rb', line 304

def cached_stream_react_component(component_name, raw_options = {}, &block)
  ReactOnRailsPro::Utils.with_trace(component_name) do
    check_caching_options!(raw_options, block)
    fetch_stream_react_component(component_name, raw_options, &block)
  end
end

#fetch_react_component(component_name, options) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
# File 'app/helpers/react_on_rails_pro_helper.rb', line 49

def fetch_react_component(component_name, options, &)
  ReactOnRailsPro::Cache.fetch_react_component(
    component_name,
    options,
    {
      on_cache_hit: lambda do |cached_component_name, cached_options|
        load_pack_for_cached_react_component(cached_component_name, cached_options)
      end
    },
    &
  )
end

#rsc_payload_react_component(component_name, options = {}) ⇒ String

Note:

This helper requires React Server Components support to be enabled in your configuration: ReactOnRailsPro.configure do |config| config.enable_rsc_support = true end

Note:

You don't have to deal directly with this helper function - it's used internally by the

Renders the React Server Component (RSC) payload for a given component. This helper generates a special format designed by React for serializing server components and transmitting them to the client.

Example NDJSON stream:

{"html":"<RSC Payload>","consoleReplayScript":"","hasErrors":false,"isShellReady":true}
{"html":"<RSC Payload>","consoleReplayScript":"console.log('Loading...')","hasErrors":false,"isShellReady":true}

The RSC payload within the html field contains:

  • The component's rendered output from the server
  • References to client components that need hydration
  • Data props passed to client components

rsc_payload_route helper function. The returned data from this function is used internally by components registered using the registerServerComponent function. Don't use it unless you need more control over the RSC payload generation. To know more about RSC payload, see the following link:

Examples:

Basic usage with a server component

<%= rsc_payload_react_component("ReactServerComponentPage") %>

With props and tracing enabled

<%= rsc_payload_react_component("RSCPostsPage",
      props: { artificialDelay: 1000 },
      trace: true) %>

Parameters:

  • component_name (String)

    The name of the React component to render. This component should be a server component or a mixed component tree containing both server and client components.

  • options (Hash) (defaults to: {})

    Options for rendering the component

Options Hash (options):

  • :props (Hash)

    Props to pass to the component (default: {})

  • :trace (Boolean)

    Enable tracing for debugging (default: false)

  • :id (String)

    Custom DOM ID for the component container (optional)

Returns:

  • (String)

    Returns a Newline Delimited JSON (NDJSON) stream where each line contains a JSON object with:

    • html: The RSC payload containing the rendered server components and client component references
    • consoleReplayScript: JavaScript to replay server-side console logs in the client
    • hasErrors: Boolean indicating if any errors occurred during rendering
    • isShellReady: Boolean indicating if the initial shell is ready for hydration

Raises:

See Also:



274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/helpers/react_on_rails_pro_helper.rb', line 274

def rsc_payload_react_component(component_name, options = {})
  # rsc_payload_react_component doesn't have the prerender option
  # Because setting prerender to false will not do anything
  options[:prerender] = true

  # Extract streaming-specific callback
  on_complete = options.delete(:on_complete)

  consumer_stream_async(on_complete:) do
    internal_rsc_payload_react_component(component_name, options)
  end
end

#rsc_payload_react_component_with_async_props(component_name, options = {}, &props_block) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
# File 'app/helpers/react_on_rails_pro_helper.rb', line 214

def rsc_payload_react_component_with_async_props(component_name, options = {}, &props_block)
  unless ReactOnRailsPro.configuration.enable_rsc_support
    raise ReactOnRailsPro::Error,
          "rsc_payload_react_component_with_async_props requires enable_rsc_support to be true. " \
          "Async props depend on React Server Components. " \
          "Set `config.enable_rsc_support = true` in your ReactOnRailsPro configuration."
  end

  options[:async_props_block] = props_block
  rsc_payload_react_component(component_name, options)
end

#stream_react_component(component_name, options = {}) ⇒ Object

Streams a server-side rendered React component using React's renderToPipeableStream. Supports React 18 features like Suspense, concurrent rendering, and selective hydration. Enables progressive rendering and improved performance for large components.

Note: This function can only be used with React on Rails Pro. The view that uses this function must be rendered using the stream_view_containing_react_components method from the React on Rails Pro gem.

Example of an async React component that can benefit from streaming:

const AsyncComponent = async () => { const data = await fetchData(); return

data
; };

function App() { return ( ); }

Any other options are passed to the content tag, including the id.

Parameters:

  • component_name (String)

    Name of your registered component

  • options (Hash) (defaults to: {})

    Options for rendering

Options Hash (options):

  • :props (Hash)

    Props to pass to the react component

  • :dom_id (String)

    DOM ID of the component container

  • :html_options (Hash)

    Options passed to content_tag

  • :trace (Boolean)

    Set to true to add extra debugging information to the HTML

  • :raise_on_prerender_error (Boolean)

    Set to true to raise exceptions during server-side rendering



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/helpers/react_on_rails_pro_helper.rb', line 157

def stream_react_component(component_name, options = {})
  # stream_react_component doesn't have the prerender option
  # Because setting prerender to false is equivalent to calling react_component with prerender: false
  options[:prerender] = true
  if options.key?(:immediate_hydration)
    ReactOnRails::Helper.warn_removed_immediate_hydration_option("stream_react_component")
    options.delete(:immediate_hydration)
  end

  # Extract streaming-specific callback
  on_complete = options.delete(:on_complete)

  consumer_stream_async(on_complete:) do
    internal_stream_react_component(component_name, options)
  end
end

#stream_react_component_with_async_props(component_name, options = {}, &props_block) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
# File 'app/helpers/react_on_rails_pro_helper.rb', line 202

def stream_react_component_with_async_props(component_name, options = {}, &props_block)
  unless ReactOnRailsPro.configuration.enable_rsc_support
    raise ReactOnRailsPro::Error,
          "stream_react_component_with_async_props requires enable_rsc_support to be true. " \
          "Async props depend on React Server Components. " \
          "Set `config.enable_rsc_support = true` in your ReactOnRailsPro configuration."
  end

  options[:async_props_block] = props_block
  stream_react_component(component_name, options)
end