Skip to content

Latest commit

 

History

History
123 lines (86 loc) · 7.24 KB

15.0.0.md

File metadata and controls

123 lines (86 loc) · 7.24 KB

React on Rails 15.0.0 Release Notes

Major Features

🚀 React Server Components Support

Experience the future of React with full RSC integration in your Rails apps:

  • Seamlessly use React Server Components
  • Reduce client bundle sizes
  • Enable powerful new patterns for data fetching
  • ⚡️ Requires React on Rails Pro - See the full tutorial

Improved Component Hydration

Major improvements to component and store hydration:

  • Components and stores now hydrate immediately rather than waiting for page load
  • Enables faster hydration, especially beneficial for streamed pages
  • Components can hydrate before the page is fully streamed
  • Can use async scripts in the page with no fear of race condition
  • No need to use defer anymore

Enhanced Script Loading Strategies

  • New configuration option generated_component_packs_loading_strategy replaces defer_generated_component_packs
  • Supports three loading strategies:
    • :async - Loads scripts asynchronously (default for Shakapacker ≥ 8.2.0)
    • :defer - Defers script execution until after page load (doesn't work well with Streamed HTML as it will wait for the full page load before hydrating the components)
    • :sync - Loads scripts synchronously (default for Shakapacker < 8.2.0) (better to upgrade to Shakapacker 8.2.0 and use :async strategy)
  • Improves page performance by optimizing how component packs are loaded

Breaking Changes

Component Hydration Changes

  • The defer_generated_component_packs configuration has been deprecated. Use generated_component_packs_loading_strategy instead.

  • The generated_component_packs_loading_strategy defaults to :async for Shakapacker ≥ 8.2.0 and :sync for Shakapacker < 8.2.0.

  • The force_load configuration now defaults to true.

  • The new default values of generated_component_packs_loading_strategy: :async and force_load: true work together to optimize component hydration. Components now hydrate as soon as their code and server-rendered HTML are available, without waiting for the full page to load. This parallel processing significantly improves time-to-interactive by eliminating the traditional waterfall of waiting for page load before beginning hydration (It's critical for streamed HTML).

    • The previous need for deferring scripts to prevent race conditions has been eliminated due to improved hydration handling. Making scripts not defer is critical to execute the hydration scripts early before the page is fully loaded.
    • The force_load configuration makes react-on-rails hydrate components immediately as soon as their server-rendered HTML reaches the client, without waiting for the full page load.
    • If you want to keep the previous behavior, you can set generated_component_packs_loading_strategy: :defer or force_load: false in your config/initializers/react_on_rails.rb file.
      • You can also keep it for individual components by passing force_load: false to react_component or stream_react_component.
    • Redux store now supports force_load option, which defaults to config.force_load (and so to true if that isn't set). If true, the Redux store will hydrate immediately as soon as its server-side data reaches the client.
      • You can override this behavior for individual Redux stores by calling the redux_store helper with force_load: false, same as react_component.
  • ReactOnRails.reactOnRailsPageLoaded() is now an async function:

    • If you manually call this function to ensure components are hydrated (e.g., with async script loading), you must now await the promise it returns:

      // Before
      ReactOnRails.reactOnRailsPageLoaded();
      // Code expecting all components to be hydrated
      
      // After
      await ReactOnRails.reactOnRailsPageLoaded();
      // Code expecting all components to be hydrated
    • If you call it in a turbolinks:load listener to work around the issue documented in Turbolinks, the listener can be safely removed.

Script Loading Strategy Migration

  • If you were previously using defer_generated_component_packs: true, use generated_component_packs_loading_strategy: :defer instead
  • If you were previously using defer_generated_component_packs: false, use generated_component_packs_loading_strategy: :sync instead
  • For optimal performance with Shakapacker ≥ 8.2.0, consider using generated_component_packs_loading_strategy: :async

ESM-only package

The package is now published as ES Modules instead of CommonJS. In most cases it shouldn't affect your code, as bundlers will be able to handle it. However:

  • If you explicitly use require('react-on-rails'), and can't change to import, upgrade to Node v20.19.0+ or v22.12.0+. They allow require for ESM modules without any flags. Node v20.17.0+ with --experimental-require-module should work as well.
  • If you run into TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. TypeScript error, you'll need to upgrade to TypeScript 5.8 and set module to nodenext.

Finally, if everything else fails, please contact us and we'll help you upgrade or release a dual ESM-CJS version.

globalThis

globalThis is now used in code. It should be available in browsers since 2020 and in Node, but in case your environment doesn't support it, you'll need to shim it using globalthis or core-js.

Store Dependencies for Components

When using Redux stores with multiple components, you need to explicitly declare store dependencies to optimize hydration. Here's how:

The Problem

If you have deferred Redux stores and components like this:

<% redux_store("SimpleStore", props: @app_props_server_render, defer: true) %>
<%= react_component('ReduxApp', {}, {prerender: true}) %>
<%= react_component('ComponentWithNoStore', {}, {prerender: true}) %>
<%= redux_store_hydration_data %>

By default, React on Rails assumes components depend on all previously created stores. This means:

  • Neither ReduxApp nor ComponentWithNoStore will hydrate until SimpleStore is hydrated
  • Since the store is deferred to the end of the page, both components are forced to wait unnecessarily

The Solution

Explicitly declare store dependencies for each component:

<% redux_store("SimpleStore", props: @app_props_server_render, defer: true) %>
<%= react_component('ReduxApp', {}, {
  prerender: true
  # No need to specify store_dependencies: it automatically depends on SimpleStore
}) %>
<%= react_component('ComponentWithNoStore', {}, {
  prerender: true,
  # Explicitly declare no store dependencies
  store_dependencies: []
}) %>
<%= redux_store_hydration_data %>

This allows ComponentWithNoStore to hydrate immediately without waiting for SimpleStore, improving page performance.