From 8cf31a496115d708245aff7fd83bba418bd9f394 Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Mon, 7 Aug 2023 21:50:45 +0330 Subject: [PATCH 01/11] Add ruby logic to replace null with undefiend in props json --- lib/react/rails/component_mount.rb | 11 ++++++++++- lib/react/rails/railtie.rb | 1 + test/react/rails/component_mount_test.rb | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index 6aef2c72..ae4098a8 100644 --- a/lib/react/rails/component_mount.rb +++ b/lib/react/rails/component_mount.rb @@ -56,7 +56,7 @@ def generate_html_options(name, options, props, prerender_options) unless prerender_options == :static html_options[:data].tap do |data| data[:react_class] = name - data[:react_props] = (props.is_a?(String) ? props : props.to_json) + data[:react_props] = props_to_json(props) data[:hydrate] = "t" if prerender_options num_components = @cache_ids.count { |c| c.start_with? name } @@ -67,6 +67,15 @@ def generate_html_options(name, options, props, prerender_options) html_options end + def props_to_json(props) + return props if props.is_a?(String) + return props.to_json unless Dummy::Application.config.react.null_to_undefined_props + + # This regex matches key:value with null values while ensuing no string with similar + # pattern gets matched. It doesn't include null values in arrays. + props.to_json.gsub(/([^\\]":)null([,}\]])/, '\1undefined\2') + end + def rendered_tag(html_options, &block) html_tag = html_options[:tag] || :div diff --git a/lib/react/rails/railtie.rb b/lib/react/rails/railtie.rb index 43a54989..1b2cec9c 100644 --- a/lib/react/rails/railtie.rb +++ b/lib/react/rails/railtie.rb @@ -12,6 +12,7 @@ class Railtie < ::Rails::Railtie config.react.jsx_transformer_class = nil # defaults to BabelTransformer config.react.camelize_props = false # pass in an underscored hash but get a camelized hash config.react.sprockets_strategy = nil # how to attach JSX to the asset pipeline (or `false` for none) + config.react.null_to_undefined_props = false # Set to true to convert null values in props into undefined # Server rendering: config.react.server_renderer_pool_size = 1 # increase if you're on JRuby diff --git a/test/react/rails/component_mount_test.rb b/test/react/rails/component_mount_test.rb index b80bc3e5..6b146a41 100644 --- a/test/react/rails/component_mount_test.rb +++ b/test/react/rails/component_mount_test.rb @@ -128,5 +128,23 @@ def self.react_rails_prerenderer assert_equal %(
rendered Foo with {"ok":true}
), rendered_component end + + test "#react_component sets null props to undefined when null_to_undefined_props set to true" do + app.config.react.null_to_undefined_props = true + + @helper.setup(DummyController) + rendered_component = @helper.react_component("Foo", { bar: nil, content: 'bar":null,' }) + + assert_includes rendered_component, '"bar":undefined,"content":"bar\\":null,"' + end + + test "#react_component passes null props as null when null_to_undefined_props set to false" do + app.config.react.null_to_undefined_props = false + + @helper.setup(DummyController) + rendered_component = @helper.react_component("Foo", { bar: nil, content: 'bar":null,' }) + + assert_includes rendered_component, ""bar":null,"content":"bar\\":null,"" + end end end From 29b768e4adf022121d147deff0910678a8ebf725 Mon Sep 17 00:00:00 2001 From: MaySoMusician Date: Wed, 3 May 2023 16:17:42 +0900 Subject: [PATCH 02/11] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a59b220e..d6242278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ _Please add entries here for your pull requests that are not yet released._ - Upgrades React-Rails' embedded react to v18.2.0. Uses node polyfill plugin & fast-text-encoder for SSR text encoding. #1290 - If using Webpacker/Shakapacker, requires upgrading to Shakapacker v7 #1274 and #1285 +#### Added +- Added option to replace `null`s in props with `undefined` via `config.react.null_to_undefined_props` in `config/application.rb` #1293 + #### Changed - The `react:component` generator now generates a function component by default #1271 From d35843fff968946e8711de1e92cf0ce3913aa6f9 Mon Sep 17 00:00:00 2001 From: MaySoMusician Date: Wed, 3 May 2023 15:56:41 +0900 Subject: [PATCH 03/11] Update docs --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f1e2f804..bc775eb5 100644 --- a/README.md +++ b/README.md @@ -528,7 +528,6 @@ use it like so: ReactUJS.getConstructor = ReactUJS.constructorFromRequireContext(require.context('components', true)); ``` - ## Server-Side Rendering You can render React components inside your Rails server with `prerender: true`: @@ -801,6 +800,26 @@ For the vast majority of cases this will get you most of the migration: - add `import PropTypes from 'prop-types'` (Webpacker only) - re-run `bundle exec rails webpacker:install:react` to update npm packages (Webpacker only) +## Other features + +### Replace `null` with `undefined` in props + +React-Rails converts `nil` to `null` while parsing props from Ruby to JavaScript. Optionally, you can configure React-Rails to parse `nil` values to `undefined` as per the following: + +```ruby +# config/application.rb +module TheAppName + class Application < Rails::Application + # ... + # Set to true to convert null values in props into undefined + config.react.null_to_undefined_props = true + # ... + end +end +``` + +More information in: [discussion#1272](https://github.com/reactjs/react-rails/discussions/1272). + ## Common Errors ### Getting warning for `Can't resolve 'react-dom/client'` in React < 18 From 62bac8a3a4611dcfc8c8a1ae79bb90d6d30c1098 Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Sun, 13 Aug 2023 20:41:00 +0330 Subject: [PATCH 04/11] Update ToC in README file --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc775eb5..6b1b4272 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ Read the [full review here](https://clutch.co/profile/shakacode#reviews?sort_by= - [Upgrading](#upgrading) - [2.7 to 3.0](#27-to-30) - [2.3 to 2.4](#23-to-24) +- [Other features](#other-features) + - [Replace `null` with `undefined` in props](#replace-null-with-undefined-in-props) - [Common Errors](#common-errors) - [Getting warning for `Can't resolve 'react-dom/client'` in React < 18](#getting-warning-for-cant-resolve-react-domclient-in-react--18) - [Undefined Set](#undefined-set) @@ -75,6 +77,7 @@ Read the [full review here](https://clutch.co/profile/shakacode#reviews?sort_by= - [HMR](#hmr) - [Related Projects](#related-projects) - [Contributing](#contributing) +- [Supporters](#supporters) @@ -876,7 +879,7 @@ By contributing to React-Rails, you agree to abide by the [code of conduct](http You can always help by submitting patches or triaging issues. Even offering reproduction steps to issues is incredibly helpful! -# Supporters +## Supporters The following companies support the development of this and other open-source projects maintained by ShakaCode by providing licenses to the ShakaCode team. ShakaCode stands by the usefulness of these products! From 01d0feb8c19be5e62ca9f5cd8dd58e49bee04ab8 Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Mon, 14 Aug 2023 17:04:39 +0330 Subject: [PATCH 05/11] Update lock files for react-rails v3 --- gemfiles/base.gemfile.lock | 11 +---------- gemfiles/shakapacker.gemfile.lock | 7 +------ gemfiles/sprockets_3.gemfile.lock | 7 +------ gemfiles/sprockets_4.gemfile.lock | 7 +------ 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/gemfiles/base.gemfile.lock b/gemfiles/base.gemfile.lock index 6ef10e35..5f99a78e 100644 --- a/gemfiles/base.gemfile.lock +++ b/gemfiles/base.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - react-rails (2.7.1) + react-rails (3.0.0) babel-transpiler (>= 0.7.0) connection_pool execjs @@ -169,10 +169,6 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.9) - nokogiri (1.14.3-x86_64-darwin) - racc (~> 1.4) - nokogiri (1.13.8-x86_64-linux) - racc (~> 1.4) nokogiri (1.13.8-x86_64-linux) racc (~> 1.4) notiffany (0.1.3) @@ -238,10 +234,6 @@ GEM timeout (0.3.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - webdrivers (5.2.0) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0) websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) @@ -271,7 +263,6 @@ DEPENDENCIES react-rails! selenium-webdriver test-unit (~> 2.5) - webdrivers BUNDLED WITH 2.4.9 diff --git a/gemfiles/shakapacker.gemfile.lock b/gemfiles/shakapacker.gemfile.lock index 286151dd..868c8e3f 100644 --- a/gemfiles/shakapacker.gemfile.lock +++ b/gemfiles/shakapacker.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - react-rails (2.7.1) + react-rails (3.0.0) babel-transpiler (>= 0.7.0) connection_pool execjs @@ -244,10 +244,6 @@ GEM timeout (0.3.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - webdrivers (5.2.0) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0) websocket (1.2.9) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) @@ -278,7 +274,6 @@ DEPENDENCIES selenium-webdriver shakapacker (= 7.0.2) test-unit (~> 2.5) - webdrivers BUNDLED WITH 2.4.9 diff --git a/gemfiles/sprockets_3.gemfile.lock b/gemfiles/sprockets_3.gemfile.lock index ced06bde..f4abc37e 100644 --- a/gemfiles/sprockets_3.gemfile.lock +++ b/gemfiles/sprockets_3.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - react-rails (2.7.1) + react-rails (3.0.0) babel-transpiler (>= 0.7.0) connection_pool execjs @@ -246,10 +246,6 @@ GEM turbolinks-source (5.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - webdrivers (4.2.0) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (>= 3.0, < 4.0) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -282,7 +278,6 @@ DEPENDENCIES sprockets-rails test-unit (~> 2.5) turbolinks (~> 5) - webdrivers BUNDLED WITH 2.4.9 diff --git a/gemfiles/sprockets_4.gemfile.lock b/gemfiles/sprockets_4.gemfile.lock index 2a4754fb..44d3fc66 100644 --- a/gemfiles/sprockets_4.gemfile.lock +++ b/gemfiles/sprockets_4.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - react-rails (2.7.1) + react-rails (3.0.0) babel-transpiler (>= 0.7.0) connection_pool execjs @@ -246,10 +246,6 @@ GEM turbolinks-source (5.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - webdrivers (4.2.0) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (>= 3.0, < 4.0) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -282,7 +278,6 @@ DEPENDENCIES sprockets-rails test-unit (~> 2.5) turbolinks (~> 5) - webdrivers BUNDLED WITH 2.4.9 From 4e7a1c62569f82a64af50bbd9f02b89149846c2b Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Mon, 14 Aug 2023 17:24:52 +0330 Subject: [PATCH 06/11] Move the changelog entry out of v3 --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6242278..aed15c74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Changes since last non-beta release. _Please add entries here for your pull requests that are not yet released._ +#### Added +- Added option to replace `null`s in props with `undefined` via `config.react.null_to_undefined_props` in `config/application.rb` #1293 + ## [3.0.0] - 2023-08-14 ### Breaking Changes @@ -17,9 +20,6 @@ _Please add entries here for your pull requests that are not yet released._ - Upgrades React-Rails' embedded react to v18.2.0. Uses node polyfill plugin & fast-text-encoder for SSR text encoding. #1290 - If using Webpacker/Shakapacker, requires upgrading to Shakapacker v7 #1274 and #1285 -#### Added -- Added option to replace `null`s in props with `undefined` via `config.react.null_to_undefined_props` in `config/application.rb` #1293 - #### Changed - The `react:component` generator now generates a function component by default #1271 From 4ee47c35ccfee80e9b78b8edb7736923cc3e29d8 Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Mon, 14 Aug 2023 17:27:45 +0330 Subject: [PATCH 07/11] Make props_to_json a pure function with options --- lib/react/rails/component_mount.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index ae4098a8..bbd4577c 100644 --- a/lib/react/rails/component_mount.rb +++ b/lib/react/rails/component_mount.rb @@ -56,7 +56,10 @@ def generate_html_options(name, options, props, prerender_options) unless prerender_options == :static html_options[:data].tap do |data| data[:react_class] = name - data[:react_props] = props_to_json(props) + data[:react_props] = props_to_json( + props, + null_to_undefined: Dummy::Application.config.react.null_to_undefined_props + ) data[:hydrate] = "t" if prerender_options num_components = @cache_ids.count { |c| c.start_with? name } @@ -67,9 +70,9 @@ def generate_html_options(name, options, props, prerender_options) html_options end - def props_to_json(props) + def props_to_json(props, options = { null_to_undefined: false }) return props if props.is_a?(String) - return props.to_json unless Dummy::Application.config.react.null_to_undefined_props + return props.to_json unless options[:null_to_undefined] # This regex matches key:value with null values while ensuing no string with similar # pattern gets matched. It doesn't include null values in arrays. From 3f8f482c45ad42f830567e937482f584ddba876a Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Mon, 14 Aug 2023 18:44:14 +0330 Subject: [PATCH 08/11] Improve tests and add tests for arrays --- test/react/rails/component_mount_test.rb | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/react/rails/component_mount_test.rb b/test/react/rails/component_mount_test.rb index 6b146a41..f5c8a459 100644 --- a/test/react/rails/component_mount_test.rb +++ b/test/react/rails/component_mount_test.rb @@ -146,5 +146,45 @@ def self.react_rails_prerenderer assert_includes rendered_component, ""bar":null,"content":"bar\\":null,"" end + + test "#props_to_json doesn't converts null values to undefined be default" do + props = { name: nil } + expected_json = '{"name":null}' + component_mount = React::Rails::ComponentMount.new + + actual_json = component_mount.send(:props_to_json, props) + + assert_equal(expected_json, actual_json) + end + + test "#props_to_json converts null values to undefined with null_to_undefined: true option" do + props = { bar: nil, content: 'bar":null,' } + expected_json = '{"bar":undefined,"content":"bar\\":null,"}' + component_mount = React::Rails::ComponentMount.new + + actual_json = component_mount.send(:props_to_json, props, { null_to_undefined: true }) + + assert_equal(expected_json, actual_json) + end + + test "#props_to_json converts null values in arrays to undefined with null_to_undefined: true option" do + props = { items1: [nil], items2: [1, nil], items3: [nil, 1], items4: [1, nil, 2] } + expected_json = '{"items1":[undefined],"items2":[1,undefined],"items3":[undefined,1],"items4":[1,undefined,2]}' + component_mount = React::Rails::ComponentMount.new + + actual_json = component_mount.send(:props_to_json, props, { null_to_undefined: true }) + + assert_equal(expected_json, actual_json) + end + + test "#props_to_json doesnt converts null-like values in arrays to undefined with null_to_undefined: true option" do + props = { items1: "[null]", items2: "[1, null]", items3: "[null, 1]", items4: "[1, null, 2]" } + expected_json = '{"items1":"[null]","items2":"[1,null]","items3":"[null,1]","items4":"[1,null,2]"}' + component_mount = React::Rails::ComponentMount.new + + actual_json = component_mount.send(:props_to_json, props, { null_to_undefined: true }) + + assert_equal(expected_json, actual_json) + end end end From 7a2b497b853ca8f360bf9e901ca40b1d5a6bacea Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Mon, 14 Aug 2023 20:03:49 +0330 Subject: [PATCH 09/11] Add array support --- lib/react/rails/component_mount.rb | 4 +++- test/react/rails/component_mount_test.rb | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index bbd4577c..701d22e7 100644 --- a/lib/react/rails/component_mount.rb +++ b/lib/react/rails/component_mount.rb @@ -76,7 +76,9 @@ def props_to_json(props, options = { null_to_undefined: false }) # This regex matches key:value with null values while ensuing no string with similar # pattern gets matched. It doesn't include null values in arrays. - props.to_json.gsub(/([^\\]":)null([,}\]])/, '\1undefined\2') + props.to_json + .gsub(/([^\\]":)null([,}\]])/, '\1undefined\2') # match simple null values + .gsub(/([^\\]":(\[[^\\"]+,|\[))null([,\]])/, '\1undefined\3') # Match nulls in array end def rendered_tag(html_options, &block) diff --git a/test/react/rails/component_mount_test.rb b/test/react/rails/component_mount_test.rb index f5c8a459..de1ede25 100644 --- a/test/react/rails/component_mount_test.rb +++ b/test/react/rails/component_mount_test.rb @@ -178,8 +178,19 @@ def self.react_rails_prerenderer end test "#props_to_json doesnt converts null-like values in arrays to undefined with null_to_undefined: true option" do - props = { items1: "[null]", items2: "[1, null]", items3: "[null, 1]", items4: "[1, null, 2]" } - expected_json = '{"items1":"[null]","items2":"[1,null]","items3":"[null,1]","items4":"[1,null,2]"}' + props = { + items1: "[null]", + items2: "[1,null]", + items3: "[null,1]", + items4: "[1,null,2]", + items5: '["a",null]', + items6: '[null,"b"]', + items7: '["a",null,"b"]', + items8: '["a",nullx,"b"]' + } + expected_json = '{"items1":"[null]","items2":"[1,null]","items3":"[null,1]","items4":"[1,null,2]",' \ + '"items5":"[\"a\",null]","items6":"[null,\"b\"]","items7":"[\"a\",null,\"b\"]"' \ + ',"items8":"[\"a\",nullx,\"b\"]"}' component_mount = React::Rails::ComponentMount.new actual_json = component_mount.send(:props_to_json, props, { null_to_undefined: true }) From ed87d9aad8b72586188fbf2ae7fbe267a0907a53 Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Mon, 14 Aug 2023 20:21:41 +0330 Subject: [PATCH 10/11] Add test for nested arrays --- test/react/rails/component_mount_test.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/react/rails/component_mount_test.rb b/test/react/rails/component_mount_test.rb index de1ede25..a5b361c8 100644 --- a/test/react/rails/component_mount_test.rb +++ b/test/react/rails/component_mount_test.rb @@ -197,5 +197,22 @@ def self.react_rails_prerenderer assert_equal(expected_json, actual_json) end + + test "#props_to_json doesnt converts null values in nested arrays to undefined with null_to_undefined: true" do + props = { + items1: nil, + items2: [1, nil, 2], + items3: nil, + items4: "[1, null, 2]", + items5: nil + } + expected_json = '{"items1":undefined,"items2":[1,undefined,2],"items3":undefined,"items4":"[1, null, 2]"' \ + ',"items5":undefined}' + component_mount = React::Rails::ComponentMount.new + + actual_json = component_mount.send(:props_to_json, props, { null_to_undefined: true }) + + assert_equal(expected_json, actual_json) + end end end From edf889f4ecd2e03c1122cd4c55e7d35cc4844992 Mon Sep 17 00:00:00 2001 From: Mostafa Ahangarha Date: Tue, 15 Aug 2023 17:28:41 +0330 Subject: [PATCH 11/11] Fix rubocop issue --- .rubocop.yml | 2 +- test/react/rails/component_mount_test.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 7dd7e917..0cb6ae06 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -86,4 +86,4 @@ Naming/RescuedExceptionsVariableName: Metrics/BlockLength: Exclude: - - 'test/**/*_test.rb' \ No newline at end of file + - 'test/**/*_test.rb' diff --git a/test/react/rails/component_mount_test.rb b/test/react/rails/component_mount_test.rb index a5b361c8..69dbcd24 100644 --- a/test/react/rails/component_mount_test.rb +++ b/test/react/rails/component_mount_test.rb @@ -2,6 +2,7 @@ require "test_helper" +# rubocop:disable Metrics/ClassLength class ComponentMountTest < ActionDispatch::IntegrationTest module DummyRenderer def self.render(component_name, props, _prerender_options) @@ -216,3 +217,4 @@ def self.react_rails_prerenderer end end end +# rubocop:enable Metrics/ClassLength