Skip to content

Commit 3c1152a

Browse files
committed
refactor(ViewHelper) move HTML tag to ComponentMount
1 parent a80805c commit 3c1152a

File tree

6 files changed

+93
-62
lines changed

6 files changed

+93
-62
lines changed

lib/react/rails.rb

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
require 'react/rails/engine'
33
require 'react/rails/railtie'
44
require 'react/rails/version'
5+
require 'react/rails/component_mount'
56
require 'react/rails/view_helper'
67
require 'react/rails/controller_renderer'

lib/react/rails/component_mount.rb

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module React
2+
module Rails
3+
class ComponentMount
4+
include ActionView::Helpers::TagHelper
5+
include ActionView::Helpers::TextHelper
6+
attr_accessor :output_buffer
7+
8+
# Render a UJS-type HTML tag annotated with data attributes, which
9+
# are used by react_ujs to actually instantiate the React component
10+
# on the client.
11+
def react_component(name, props = {}, options = {}, &block)
12+
options = {:tag => options} if options.is_a?(Symbol)
13+
14+
prerender_options = options[:prerender]
15+
if prerender_options
16+
block = Proc.new{ concat React::ServerRendering.render(name, props, prerender_options) }
17+
end
18+
19+
html_options = options.reverse_merge(:data => {})
20+
html_options[:data].tap do |data|
21+
data[:react_class] = name
22+
data[:react_props] = (props.is_a?(String) ? props : props.to_json)
23+
end
24+
html_tag = html_options[:tag] || :div
25+
26+
# remove internally used properties so they aren't rendered to DOM
27+
html_options.except!(:tag, :prerender)
28+
29+
content_tag(html_tag, '', html_options, &block)
30+
end
31+
end
32+
end
33+
end

lib/react/rails/railtie.rb

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class Railtie < ::Rails::Railtie
1515
config.react.server_renderer_timeout = 20 # seconds
1616
config.react.server_renderer = nil # defaults to SprocketsRenderer
1717
config.react.server_renderer_options = {} # SprocketsRenderer provides defaults
18+
# View helper implementation:
19+
config.react.view_helper_implementation = nil # Defaults to ComponentMount
1820

1921
# Watch .jsx files for changes in dev, so we can reload the JS VMs with the new JS code.
2022
initializer "react_rails.add_watchable_files", group: :all do |app|
@@ -27,6 +29,8 @@ class Railtie < ::Rails::Railtie
2729
React::JSX.transformer_class = app.config.react.jsx_transformer_class
2830
React::JSX.transform_options = app.config.react.jsx_transform_options
2931

32+
app.config.react.view_helper_implementation ||= React::Rails::ComponentMount
33+
React::Rails::ViewHelper.helper_implementation_class = app.config.react.view_helper_implementation
3034
ActiveSupport.on_load(:action_view) do
3135
include ::React::Rails::ViewHelper
3236
end

lib/react/rails/view_helper.rb

+6-21
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,13 @@
11
module React
22
module Rails
33
module ViewHelper
4-
# Render a UJS-type HTML tag annotated with data attributes, which
5-
# are used by react_ujs to actually instantiate the React component
6-
# on the client.
7-
def react_component(name, props = {}, options = {}, &block)
8-
options = {:tag => options} if options.is_a?(Symbol)
4+
# This class will be used for inserting tags into HTML.
5+
# It should implement react_component(name, props, options &block)
6+
# The default is {React::Rails::ComponentMount}
7+
mattr_accessor :helper_implementation_class
98

10-
prerender_options = options[:prerender]
11-
if prerender_options
12-
block = Proc.new{ concat React::ServerRendering.render(name, props, prerender_options) }
13-
end
14-
15-
html_options = options.reverse_merge(:data => {})
16-
html_options[:data].tap do |data|
17-
data[:react_class] = name
18-
data[:react_props] = (props.is_a?(String) ? props : props.to_json)
19-
end
20-
html_tag = html_options[:tag] || :div
21-
22-
# remove internally used properties so they aren't rendered to DOM
23-
html_options.except!(:tag, :prerender)
24-
25-
content_tag(html_tag, '', html_options, &block)
9+
def react_component(*args, &block)
10+
self.helper_implementation_class.new.react_component(*args, &block)
2611
end
2712
end
2813
end
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'test_helper'
2+
3+
class ComponentMountTest < ActionDispatch::IntegrationTest
4+
setup do
5+
@helper = React::Rails::ComponentMount.new
6+
end
7+
8+
test '#react_component accepts React props' do
9+
html = @helper.react_component('Foo', {bar: 'value'})
10+
expected_props = %w(data-react-class="Foo" data-react-props="{&quot;bar&quot;:&quot;value&quot;}")
11+
expected_props.each do |segment|
12+
assert html.include?(segment)
13+
end
14+
end
15+
16+
test '#react_component accepts jbuilder-based strings as properties' do
17+
jbuilder_json = Jbuilder.new do |json|
18+
json.bar 'value'
19+
end.target!
20+
21+
html = @helper.react_component('Foo', jbuilder_json)
22+
expected_props = %w(data-react-class="Foo" data-react-props="{&quot;bar&quot;:&quot;value&quot;}")
23+
expected_props.each do |segment|
24+
assert html.include?(segment), "expected #{html} to include #{segment}"
25+
end
26+
end
27+
28+
test '#react_component accepts string props with prerender: true' do
29+
html = @helper.react_component('Todo', {todo: 'render on the server'}.to_json, prerender: true)
30+
assert(html.include?('data-react-class="Todo"'), "it includes attrs for UJS")
31+
assert(html.include?('>render on the server</li>'), "it includes rendered HTML")
32+
assert(html.include?('data-reactid'), "it includes React properties")
33+
end
34+
35+
test '#react_component passes :static to SprocketsRenderer' do
36+
html = @helper.react_component('Todo', {todo: 'render on the server'}.to_json, prerender: :static)
37+
assert(html.include?('>render on the server</li>'), "it includes rendered HTML")
38+
assert(!html.include?('data-reactid'), "it DOESNT INCLUDE React properties")
39+
end
40+
41+
test '#react_component accepts HTML options and HTML tag' do
42+
assert @helper.react_component('Foo', {}, :span).match(/<span\s.*><\/span>/)
43+
44+
html = @helper.react_component('Foo', {}, {class: 'test', tag: :span, data: {foo: 1}})
45+
assert html.match(/<span\s.*><\/span>/)
46+
assert html.include?('class="test"')
47+
assert html.include?('data-foo="1"')
48+
end
49+
end

test/react/rails/view_helper_test.rb

-41
Original file line numberDiff line numberDiff line change
@@ -18,50 +18,9 @@ class ViewHelperTest < ActionDispatch::IntegrationTest
1818
include Capybara::DSL
1919

2020
setup do
21-
@helper = ActionView::Base.new.extend(React::Rails::ViewHelper)
2221
Capybara.current_driver = Capybara.javascript_driver
2322
end
2423

25-
test 'react_component accepts React props' do
26-
html = @helper.react_component('Foo', {bar: 'value'})
27-
%w(data-react-class="Foo" data-react-props="{&quot;bar&quot;:&quot;value&quot;}").each do |segment|
28-
assert html.include?(segment)
29-
end
30-
end
31-
32-
test 'react_component accepts jbuilder-based strings as properties' do
33-
jbuilder_json = Jbuilder.new do |json|
34-
json.bar 'value'
35-
end.target!
36-
37-
html = @helper.react_component('Foo', jbuilder_json)
38-
%w(data-react-class="Foo" data-react-props="{&quot;bar&quot;:&quot;value&quot;}").each do |segment|
39-
assert html.include?(segment), "expected #{html} to include #{segment}"
40-
end
41-
end
42-
43-
test 'react_component accepts string props with prerender: true' do
44-
html = @helper.react_component('Todo', {todo: 'render on the server'}.to_json, prerender: true)
45-
assert(html.include?('data-react-class="Todo"'), "it includes attrs for UJS")
46-
assert(html.include?('>render on the server</li>'), "it includes rendered HTML")
47-
assert(html.include?('data-reactid'), "it includes React properties")
48-
end
49-
50-
test 'react_component passes :static to SprocketsRenderer' do
51-
html = @helper.react_component('Todo', {todo: 'render on the server'}.to_json, prerender: :static)
52-
assert(html.include?('>render on the server</li>'), "it includes rendered HTML")
53-
assert(!html.include?('data-reactid'), "it DOESNT INCLUDE React properties")
54-
end
55-
56-
test 'react_component accepts HTML options and HTML tag' do
57-
assert @helper.react_component('Foo', {}, :span).match(/<span\s.*><\/span>/)
58-
59-
html = @helper.react_component('Foo', {}, {:class => 'test', :tag => :span, :data => {:foo => 1}})
60-
assert html.match(/<span\s.*><\/span>/)
61-
assert html.include?('class="test"')
62-
assert html.include?('data-foo="1"')
63-
end
64-
6524
test 'ujs object present on the global React object and has our methods' do
6625
visit '/pages/1'
6726
assert page.has_content?('Hello Bob')

0 commit comments

Comments
 (0)