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/CHANGELOG.md b/CHANGELOG.md index a59b220e..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 diff --git a/README.md b/README.md index f1e2f804..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) @@ -528,7 +531,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 +803,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 @@ -857,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! 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 diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index 6aef2c72..701d22e7 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.is_a?(String) ? props : props.to_json) + 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,6 +70,17 @@ def generate_html_options(name, options, props, prerender_options) html_options end + def props_to_json(props, options = { null_to_undefined: false }) + return props if props.is_a?(String) + 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. + 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) 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..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) @@ -128,5 +129,92 @@ def self.react_rails_prerenderer assert_equal %(