Skip to content

Commit

Permalink
Add random dom id option (#1121)
Browse files Browse the repository at this point in the history
* Added configuration option random_dom_id
* Added method RenderOptions has_random_dom_id?
This new global and react_component helper option allows configuring
whether or not React on Rails will automatically add a random id to the
DOM node ID.
  • Loading branch information
justin808 authored Jul 23, 2018
1 parent 15ba752 commit 82efd0b
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 426 deletions.
15 changes: 15 additions & 0 deletions docs/api/view-helpers-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

Once the bundled files have been generated in your `app/assets/webpack` folder and you have registered your components, you will want to render these components on your Rails views using the included helper method, `react_component`.

------------

### react_component

```ruby
Expand All @@ -24,11 +26,13 @@ react_component(component_name,
- **id:** Id for the div, will be used to attach the React component. This will get assigned automatically if you do not provide an id. Must be unique.
- **html_options:** Any other HTML options get placed on the added div for the component. For example, you can set a class (or inline style) on the outer div so that it behaves like a span, with the styling of `display:inline-block`.
- **trace:** set to true to print additional debugging information in the browser. Defaults to true for development, off otherwise. Only on the **client side** will you will see the `railsContext` and your props.
- **random_dom_id:** True to automatically generate random dom ids when using multiple instances of the same React component on one Rails view.
- **options if prerender (server rendering) is true:**
- **replay_console:** Default is true. False will disable echoing server-rendering logs to the browser. While this can make troubleshooting server rendering difficult, so long as you have the configuration of `logging_on_server` set to true, you'll still see the errors on the server.
- **logging_on_server:** Default is true. True will log JS console messages and errors to the server.
- **raise_on_prerender_error:** Default is false. True will throw an error on the server side rendering. Your controller will have to handle the error.

-------------

### react_component_hash

Expand Down Expand Up @@ -68,6 +72,8 @@ export default (props, _railsContext) => {

```

------------

### cached_react_component and cached_react_component_hash
Fragment caching is a [React on Rails Pro](https://github.com/shakacode/react_on_rails/wiki) feature. The API is the same as the above, but for 2 differences:

Expand All @@ -79,10 +85,13 @@ Fragment caching is a [React on Rails Pro](https://github.com/shakacode/react_on
some_slow_method_that_returns_props
end %>
```
------------
### rails_context
You can call `rails_context(server_side: true | false)` from your controller or view to see what values are are in the Rails Context. Pass true or false depending on whether you want to see the server side or the client side rails_context.
------------
### Renderer Functions (function that will call ReactDOM.render or ReactDOM.hydrate)
Expand All @@ -92,6 +101,8 @@ Why would you want to call `ReactDOM.hydrate` yourself? One possible use case is
Renderer functions are not meant to be used on the server since there's no DOM on the server. Instead, use a generator function. Attempting to server render with a renderer function will throw an error.
------------
### React Router
[React Router](https://github.com/reactjs/react-router) is supported, including server-side rendering! See:
Expand All @@ -100,6 +111,8 @@ Renderer functions are not meant to be used on the server since there's no DOM o
2. Examples in [spec/dummy/app/views/react_router](../../spec/dummy/app/views/react_router) and follow to the JavaScript code in the [spec/dummy/client/app/startup/ServerRouterApp.jsx](../../spec/dummy/client/app/startup/ServerRouterApp.jsx).
3. [Code Splitting docs](../misc-pending/code-splitting.md) for information about how to set up code splitting for server rendered routes.
------------
## server_render_js
`server_render_js(js_expression, options = {})`
Expand All @@ -109,6 +122,8 @@ Renderer functions are not meant to be used on the server since there's no DOM o
This is a helper method that takes any JavaScript expression and returns the output from evaluating it. If you have more than one line that needs to be executed, wrap it in an IIFE. JS exceptions will be caught and console messages handled properly.
------------
# More details
See the [lib/react_on_rails/helper.rb](../../lib/react_on_rails/helper.rb) source.
Expand Down
9 changes: 9 additions & 0 deletions docs/basics/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ ReactOnRails.configure do |config|
# setInterval, clearTimout when server rendering.
config.trace = Rails.env.development?

# Configure if default DOM IDs have a random value or are fixed.
# false ==> Sets the dom id to "#{react_component_name}-react-component"
# true ==> Adds "-#{SecureRandom.uuid}" to that ID
# If you might use multiple instances of the same React component on a Rails page, then
# it is convenient to set this to true or else you have to either manually set the ids to
# avoid collisions. Most newer apps will have only one instance of a component on a page,
# so this should be false in most cases.
# This value can be overrident for a given call to react_component
config.random_dom_id = false # default is true

# defaults to "" (top level)
#
Expand Down
9 changes: 6 additions & 3 deletions lib/react_on_rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def self.configure
DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze
DEFAULT_SERVER_RENDER_TIMEOUT = 20
DEFAULT_POOL_SIZE = 1
DEFAULT_RANDOM_DOM_ID = TRUE # for backwards compatability

def self.configuration
@configuration ||= Configuration.new(
Expand All @@ -32,7 +33,8 @@ def self.configuration
server_render_method: nil,
symlink_non_digested_assets_regex: nil,
build_test_command: "",
build_production_command: ""
build_production_command: "",
random_dom_id: DEFAULT_RANDOM_DOM_ID
)
end

Expand All @@ -45,7 +47,7 @@ class Configuration
:webpack_generated_files, :rendering_extension, :build_test_command,
:build_production_command,
:i18n_dir, :i18n_yml_dir,
:server_render_method, :symlink_non_digested_assets_regex
:server_render_method, :symlink_non_digested_assets_regex, :random_dom_id

def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
replay_console: nil,
Expand All @@ -56,7 +58,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
generated_assets_dir: nil, webpack_generated_files: nil,
rendering_extension: nil, build_test_command: nil,
build_production_command: nil,
i18n_dir: nil, i18n_yml_dir: nil,
i18n_dir: nil, i18n_yml_dir: nil, random_dom_id: nil,
server_render_method: nil, symlink_non_digested_assets_regex: nil)
self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
self.server_bundle_js_file = server_bundle_js_file
Expand All @@ -67,6 +69,7 @@ def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender
self.i18n_dir = i18n_dir
self.i18n_yml_dir = i18n_yml_dir

self.random_dom_id = random_dom_id
self.prerender = prerender
self.replay_console = replay_console
self.logging_on_server = logging_on_server
Expand Down
1 change: 1 addition & 0 deletions lib/react_on_rails/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def env_stylesheet_link_tag(args = {})
# raise_on_prerender_error: <true/false> Default to false. True will raise exception on server
# if the JS code throws
# Any other options are passed to the content tag, including the id.
# random_dom_id can be set to override the global default.
def react_component(component_name, options = {})
internal_result = internal_react_component(component_name, options)
server_rendered_html = internal_result[:result]["html"]
Expand Down
26 changes: 24 additions & 2 deletions lib/react_on_rails/react_component/render_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,26 @@ def props
options.fetch(:props) { NO_PROPS }
end

def random_dom_id
retrieve_key(:random_dom_id)
end

def dom_id
@dom_id ||= options.fetch(:id) { generate_unique_dom_id }
@dom_id ||= options.fetch(:id) do
if random_dom_id
generate_unique_dom_id
else
base_dom_id
end
end
end

def has_random_dom_id?
return false if options[:id]

return false unless random_dom_id

true
end

def html_options
Expand Down Expand Up @@ -58,8 +76,12 @@ def to_s

attr_reader :options

def base_dom_id
"#{react_component_name}-react-component"
end

def generate_unique_dom_id
"#{react_component_name}-react-component-#{SecureRandom.uuid}"
"#{base_dom_id}-#{SecureRandom.uuid}"
end

def retrieve_key(key)
Expand Down
4 changes: 2 additions & 2 deletions spec/dummy/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ GIT
PATH
remote: ../..
specs:
react_on_rails (11.0.9)
react_on_rails (11.0.10)
addressable
connection_pool
execjs (~> 2.5)
Expand Down Expand Up @@ -352,4 +352,4 @@ DEPENDENCIES
webpacker

BUNDLED WITH
1.16.2
1.16.3
1 change: 1 addition & 0 deletions spec/dummy/config/initializers/react_on_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def self.custom_context(view_context)
end

ReactOnRails.configure do |config|
config.random_dom_id = false # default is true
config.node_modules_location = "client" # Pre 9.0.0 always used "client"
config.build_production_command = "yarn run build:production"
config.build_test_command = "yarn run build:test"
Expand Down
70 changes: 63 additions & 7 deletions spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,34 @@
{ name: "My Test Name" }
end

let(:react_component_div) do
let(:react_component_random_id_div) do
'<div id="App-react-component-0"></div>'
end

let(:react_component_div) do
'<div id="App-react-component"></div>'
end

let(:id) { "App-react-component-0" }

let(:react_definition_script) do
let(:react_definition_script_random) do
<<-SCRIPT.strip_heredoc
<script type="application/json" class="js-react-on-rails-component" \
data-component-name="App" data-dom-id="App-react-component-0">{"name":"My Test Name"}</script>
SCRIPT
end

let(:react_definition_script) do
<<-SCRIPT.strip_heredoc
<script type="application/json" class="js-react-on-rails-component" \
data-component-name="App" data-dom-id="App-react-component">{"name":"My Test Name"}</script>
SCRIPT
end

let(:react_definition_script_no_params) do
<<-SCRIPT.strip_heredoc
<script type="application/json" class="js-react-on-rails-component" \
data-component-name="App" data-dom-id="App-react-component-0">{}</script>
data-component-name="App" data-dom-id="App-react-component">{}</script>
SCRIPT
end

Expand All @@ -121,7 +132,7 @@
it { is_expected.to include json_props_sanitized }
end

describe "API with component name only" do
describe "API with component name only (no props or other options)" do
subject { react_component("App") }
it { is_expected.to be_an_instance_of ActiveSupport::SafeBuffer }
it { is_expected.to include react_component_div }
Expand All @@ -140,6 +151,51 @@
expect(is_expected.target).to script_tag_be_included(react_definition_script)
}

context "with 'random_dom_id' false option" do
subject { react_component("App", props: props, random_dom_id: false) }

let(:react_definition_script) do
<<-SCRIPT.strip_heredoc
<script type="application/json" class="js-react-on-rails-component" data-component-name="App" data-dom-id="App-react-component">{"name":"My Test Name"}</script>
SCRIPT
end

it { is_expected.to include '<div id="App-react-component"></div>' }
it { expect(is_expected.target).to script_tag_be_included(react_definition_script) }
end

context "with 'random_dom_id' false option" do
subject { react_component("App", props: props, random_dom_id: true) }

let(:react_definition_script) do
<<-SCRIPT.strip_heredoc
<script type="application/json" class="js-react-on-rails-component" data-component-name="App" data-dom-id="App-react-component-0">{"name":"My Test Name"}</script>
SCRIPT
end

it { is_expected.to include '<div id="App-react-component-0"></div>' }
it { expect(is_expected.target).to script_tag_be_included(react_definition_script) }
end

context "with 'random_dom_id' global" do
around(:example) do |example|
ReactOnRails.configure { |config| config.random_dom_id = false }
example.run
ReactOnRails.configure { |config| config.random_dom_id = true }
end

subject { react_component("App", props: props) }

let(:react_definition_script) do
<<-SCRIPT.strip_heredoc
<script type="application/json" class="js-react-on-rails-component" data-component-name="App" data-dom-id="App-react-component">{"name":"My Test Name"}</script>
SCRIPT
end

it { is_expected.to include '<div id="App-react-component"></div>' }
it { expect(is_expected.target).to script_tag_be_included(react_definition_script) }
end

context "with 'id' option" do
subject { react_component("App", props: props, id: id) }

Expand All @@ -152,7 +208,7 @@
end

it { is_expected.to include id }
it { is_expected.not_to include react_component_div }
it { is_expected.not_to include react_component_random_id_div }
it {
expect(is_expected.target).to script_tag_be_included(react_definition_script)
}
Expand Down Expand Up @@ -217,6 +273,8 @@
ReactOnRails.configuration.rendering_extension = nil
end

after { ReactOnRails.configuration.rendering_extension = @rendering_extension }

it "should not throw an error if not in a view" do
class PlainClass
include ReactOnRailsHelper
Expand All @@ -226,7 +284,5 @@ class PlainClass
expect { ob.send(:rails_context, server_side: true) }.to_not raise_error
expect { ob.send(:rails_context, server_side: false) }.to_not raise_error
end

after { ReactOnRails.configuration.rendering_extension = @rendering_extension }
end
end
Loading

0 comments on commit 82efd0b

Please sign in to comment.