Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,26 @@ Changes since the last non-beta release.

- **Removed Pro Warning Badge**: Removed the visual warning badge that appeared when non-Pro users attempted to enable Pro-only features like `immediate_hydration`. Pro features are now silently disabled when a Pro license is not available, providing a cleaner user experience without intrusive warning banners. [PR 1993](https://github.com/shakacode/react_on_rails/pull/1993) by [AbanoubGhadban](https://github.com/AbanoubGhadban).

- **`immediate_hydration` now automatically enabled for Pro users**: The `config.immediate_hydration` configuration option has been removed. Immediate hydration is now automatically enabled for React on Rails Pro users and disabled for non-Pro users, simplifying configuration while providing optimal performance by default. Component-level overrides are still supported via the `immediate_hydration` parameter on `react_component`, `redux_store`, and `stream_react_component` helpers. [PR 1997](https://github.com/shakacode/react_on_rails/pull/1997) by [AbanoubGhadban](https://github.com/AbanoubGhadban).

#### Bug Fixes

- **Use as Git dependency**: All packages can now be installed as Git dependencies. This is useful for development and testing purposes. See [CONTRIBUTING.md](./CONTRIBUTING.md#git-dependencies) for documentation. [PR #1873](https://github.com/shakacode/react_on_rails/pull/1873) by [alexeyr-ci2](https://github.com/alexeyr-ci2).

#### Breaking Changes

- **`config.immediate_hydration` configuration removed**: The `config.immediate_hydration` setting in `config/initializers/react_on_rails.rb` has been removed. Immediate hydration is now automatically enabled for React on Rails Pro users and automatically disabled for non-Pro users.

**Migration steps:**

- Remove any `config.immediate_hydration = true` or `config.immediate_hydration = false` lines from your `config/initializers/react_on_rails.rb` file
- Pro users: No action needed - immediate hydration is now enabled automatically for optimal performance
- Non-Pro users: No action needed - standard hydration behavior continues to work as before
- Component-level overrides: You can still override behavior per-component using `react_component("MyComponent", immediate_hydration: false)` or `redux_store("MyStore", immediate_hydration: true)`
- If a non-Pro user explicitly sets `immediate_hydration: true` on a component, a warning will be logged and the component will fall back to standard hydration

[PR 1997](https://github.com/shakacode/react_on_rails/pull/1997) by [AbanoubGhadban](https://github.com/AbanoubGhadban).

- **React on Rails Core Package**: Several Pro-only methods have been removed from the core package and are now exclusively available in the `react-on-rails-pro` package. If you're using any of the following methods, you'll need to migrate to React on Rails Pro:
- `getOrWaitForComponent()`
- `getOrWaitForStore()`
Expand Down
7 changes: 2 additions & 5 deletions lib/react_on_rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ def self.configuration
components_subdirectory: nil,
make_generated_server_bundle_the_entrypoint: false,
defer_generated_component_packs: false,
# React on Rails Pro (licensed) feature - enables immediate hydration of React components
immediate_hydration: false,
# Maximum time in milliseconds to wait for client-side component registration after page load.
# If exceeded, an error will be thrown for server-side rendered components not registered on the client.
# Set to 0 to disable the timeout and wait indefinitely for component registration.
Expand All @@ -64,7 +62,7 @@ class Configuration
:server_render_method, :random_dom_id, :auto_load_bundle,
:same_bundle_for_client_and_server, :rendering_props_extension,
:make_generated_server_bundle_the_entrypoint,
:generated_component_packs_loading_strategy, :immediate_hydration,
:generated_component_packs_loading_strategy,
:component_registry_timeout,
:server_bundle_output_path, :enforce_private_server_bundles

Expand All @@ -81,7 +79,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
same_bundle_for_client_and_server: nil,
i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil,
random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
components_subdirectory: nil, auto_load_bundle: nil, immediate_hydration: nil,
components_subdirectory: nil, auto_load_bundle: nil,
component_registry_timeout: nil, server_bundle_output_path: nil, enforce_private_server_bundles: nil)
self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
self.generated_assets_dirs = generated_assets_dirs
Expand Down Expand Up @@ -122,7 +120,6 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
self.auto_load_bundle = auto_load_bundle
self.make_generated_server_bundle_the_entrypoint = make_generated_server_bundle_the_entrypoint
self.defer_generated_component_packs = defer_generated_component_packs
self.immediate_hydration = immediate_hydration
self.generated_component_packs_loading_strategy = generated_component_packs_loading_strategy
self.server_bundle_output_path = server_bundle_output_path
self.enforce_private_server_bundles = enforce_private_server_bundles
Expand Down
6 changes: 3 additions & 3 deletions lib/react_on_rails/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ module Controller
# JavaScript code.
# props: Named parameter props which is a Ruby Hash or JSON string which contains the properties
# to pass to the redux store.
# immediate_hydration: React on Rails Pro (licensed) feature. Pass as true if you wish to hydrate this
# store immediately instead of waiting for the page to load.
# immediate_hydration: React on Rails Pro (licensed) feature. When nil (default), Pro users get
# immediate hydration, non-Pro users don't. Can be explicitly overridden.
#
# Be sure to include view helper `redux_store_hydration_data` at the end of your layout or view
# or else there will be no client side hydration of your stores.
def redux_store(store_name, props: {}, immediate_hydration: nil)
immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
immediate_hydration = ReactOnRails::ProUtils.immediate_hydration_enabled? if immediate_hydration.nil?
redux_store_data = { store_name: store_name,
props: props,
immediate_hydration: immediate_hydration }
Expand Down
10 changes: 2 additions & 8 deletions lib/react_on_rails/doctor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ def analyze_server_rendering_config(content)
end
# rubocop:enable Metrics/AbcSize

# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def analyze_performance_config(content)
checker.add_info("\n⚡ Performance & Loading:")

Expand Down Expand Up @@ -732,19 +732,13 @@ def analyze_performance_config(content)
auto_load_match = content.match(/config\.auto_load_bundle\s*=\s*([^\s\n,]+)/)
checker.add_info(" auto_load_bundle: #{auto_load_match[1]}") if auto_load_match

# Immediate hydration (Pro feature)
immediate_hydration_match = content.match(/config\.immediate_hydration\s*=\s*([^\s\n,]+)/)
if immediate_hydration_match
checker.add_info(" immediate_hydration: #{immediate_hydration_match[1]} (React on Rails Pro)")
end

# Component registry timeout
timeout_match = content.match(/config\.component_registry_timeout\s*=\s*([^\s\n,]+)/)
return unless timeout_match

checker.add_info(" component_registry_timeout: #{timeout_match[1]}ms")
end
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
# rubocop:enable Metrics/CyclomaticComplexity

# rubocop:disable Metrics/AbcSize
def analyze_development_config(content)
Expand Down
6 changes: 3 additions & 3 deletions lib/react_on_rails/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ def react_component_hash(component_name, options = {})
# props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
# Options
# defer: false -- pass as true if you wish to render this below your component.
# immediate_hydration: false -- React on Rails Pro (licensed) feature. Pass as true if you wish to
# hydrate this store immediately instead of waiting for the page to load.
# immediate_hydration: nil -- React on Rails Pro (licensed) feature. When nil (default), Pro users
# get immediate hydration, non-Pro users don't. Can be explicitly overridden.
def redux_store(store_name, props: {}, defer: false, immediate_hydration: nil)
immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
immediate_hydration = ReactOnRails::ProUtils.immediate_hydration_enabled? if immediate_hydration.nil?

redux_store_data = { store_name: store_name,
props: props,
Expand Down
2 changes: 0 additions & 2 deletions lib/react_on_rails/pro_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ def generate_component_script(render_options)
# Generates the complete store hydration script tag.
# Handles both immediate hydration (Pro feature) and standard cases.
def generate_store_script(redux_store_data)
redux_store_data = ReactOnRails::ProUtils.disable_pro_render_options_if_not_licensed(redux_store_data)

store_hydration_data = content_tag(:script,
json_safe_and_pretty(redux_store_data[:props]).html_safe,
type: "application/json",
Expand Down
25 changes: 5 additions & 20 deletions lib/react_on_rails/pro_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,17 @@

module ReactOnRails
module ProUtils
PRO_ONLY_OPTIONS = %i[immediate_hydration].freeze

# Checks if React on Rails Pro features are available
# @return [Boolean] true if Pro is installed and licensed, false otherwise
def self.support_pro_features?
ReactOnRails::Utils.react_on_rails_pro?
end

def self.disable_pro_render_options_if_not_licensed(raw_options)
return raw_options if support_pro_features?

raw_options_after_disable = raw_options.dup

PRO_ONLY_OPTIONS.each do |option|
# Determine if this option is enabled (either explicitly or via global config)
option_enabled = if raw_options[option].nil?
ReactOnRails.configuration.send(option)
else
raw_options[option]
end

# Silently disable the option if it's enabled but Pro is not available
raw_options_after_disable[option] = false if option_enabled
end

raw_options_after_disable
# Returns whether immediate hydration should be enabled
# Pro users always get immediate hydration, non-Pro users never do
# @return [Boolean] true if Pro is available, false otherwise
def self.immediate_hydration_enabled?
support_pro_features?
end
end
end
17 changes: 15 additions & 2 deletions lib/react_on_rails/react_component/render_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class RenderOptions
# TODO: remove the required for named params
def initialize(react_component_name: required("react_component_name"), options: required("options"))
@react_component_name = react_component_name.camelize
@options = ReactOnRails::ProUtils.disable_pro_render_options_if_not_licensed(options)
@options = options
end

attr_reader :react_component_name
Expand Down Expand Up @@ -97,7 +97,20 @@ def logging_on_server
end

def immediate_hydration
retrieve_configuration_value_for(:immediate_hydration)
explicit_value = options[:immediate_hydration]

# Warn if non-Pro user explicitly sets immediate_hydration: true
if explicit_value == true && !ReactOnRails::Utils.react_on_rails_pro?
Rails.logger.warn <<~WARNING
[REACT ON RAILS] Warning: immediate_hydration: true requires a React on Rails Pro license.
Component '#{react_component_name}' will fall back to standard hydration behavior.
Visit https://www.shakacode.com/react-on-rails-pro/ for licensing information.
WARNING
end

options.fetch(:immediate_hydration) do
ReactOnRails::ProUtils.immediate_hydration_enabled?
end
end

def to_s
Expand Down
1 change: 0 additions & 1 deletion spec/dummy/config/initializers/react_on_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,5 @@ def self.adjust_props_for_client_side_hydration(component_name, props)
config.rendering_props_extension = RenderingPropsExtension
config.components_subdirectory = "startup"
config.auto_load_bundle = true
config.immediate_hydration = false
config.generated_component_packs_loading_strategy = :defer
end
11 changes: 2 additions & 9 deletions spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,8 @@ def self.pro_attribution_comment
stub_const("ReactOnRailsPro", pro_module)
stub_const("ReactOnRailsPro::Utils", utils_module)

# Configure immediate_hydration to true for tests since they expect that behavior
ReactOnRails.configure do |config|
config.immediate_hydration = true
end
end

after do
# Reset to default - avoid validation issues by setting directly
ReactOnRails.configuration.immediate_hydration = false
# Stub immediate_hydration_enabled? to return true for tests since they expect that behavior
allow(ReactOnRails::ProUtils).to receive(:immediate_hydration_enabled?).and_return(true)
end

let(:hash) do
Expand Down
7 changes: 2 additions & 5 deletions spec/dummy/spec/system/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,9 @@ def self.pro_attribution_comment
end
stub_const("ReactOnRailsPro", pro_module)
stub_const("ReactOnRailsPro::Utils", utils_module)
end

around do |example|
ReactOnRails.configure { |config| config.immediate_hydration = true }
example.run
ReactOnRails.configure { |config| config.immediate_hydration = false }
# Stub immediate_hydration_enabled? to return true for tests since they expect that behavior
allow(ReactOnRails::ProUtils).to receive(:immediate_hydration_enabled?).and_return(true)
end
end

Expand Down
83 changes: 82 additions & 1 deletion spec/react_on_rails/react_component/render_options_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
replay_console
raise_on_prerender_error
random_dom_id
immediate_hydration
].freeze

def the_attrs(react_component_name: "App", options: {})
Expand Down Expand Up @@ -164,4 +163,86 @@ def the_attrs(react_component_name: "App", options: {})
end
end
end

describe "#immediate_hydration" do
context "with Pro license" do
before do
allow(ReactOnRails::Utils).to receive(:react_on_rails_pro?).and_return(true)
end

context "with immediate_hydration option set to true" do
it "returns true" do
options = { immediate_hydration: true }
attrs = the_attrs(options: options)

opts = described_class.new(**attrs)

expect(opts.immediate_hydration).to be true
end
end

context "with immediate_hydration option set to false" do
it "returns false (override)" do
options = { immediate_hydration: false }
attrs = the_attrs(options: options)

opts = described_class.new(**attrs)

expect(opts.immediate_hydration).to be false
end
end

context "without immediate_hydration option" do
it "returns true (Pro default)" do
allow(ReactOnRails::ProUtils).to receive(:immediate_hydration_enabled?).and_return(true)
attrs = the_attrs

opts = described_class.new(**attrs)

expect(opts.immediate_hydration).to be true
end
end
end

context "without Pro license" do
before do
allow(ReactOnRails::Utils).to receive(:react_on_rails_pro?).and_return(false)
end

context "with immediate_hydration option set to true (not recommended)" do
it "returns true but logs a warning" do
options = { immediate_hydration: true }
attrs = the_attrs(options: options)

expect(Rails.logger).to receive(:warn).with(/immediate_hydration: true requires a React on Rails Pro license/)

opts = described_class.new(**attrs)

expect(opts.immediate_hydration).to be true
end
end

context "with immediate_hydration option set to false" do
it "returns false" do
options = { immediate_hydration: false }
attrs = the_attrs(options: options)

opts = described_class.new(**attrs)

expect(opts.immediate_hydration).to be false
end
end

context "without immediate_hydration option" do
it "returns false (non-Pro default)" do
allow(ReactOnRails::ProUtils).to receive(:immediate_hydration_enabled?).and_return(false)
attrs = the_attrs

opts = described_class.new(**attrs)

expect(opts.immediate_hydration).to be false
end
end
end
end
end
Loading