Skip to content

Commit 11748b6

Browse files
committed
Merge remote-tracking branch 'upstream/master' into generators
2 parents 60e9424 + be22558 commit 11748b6

File tree

9 files changed

+106
-23
lines changed

9 files changed

+106
-23
lines changed

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ As with all gem dependencies, we strongly recommend adding `react-rails` to your
2323
# If you missed a warning at the top of this README - this is still in development
2424
# which means this version is not pushed to rubygems.org
2525

26-
gem 'react-rails', '~> 1.0.0'
26+
gem 'react-rails', '~> 1.0.0.pre', github: 'reactjs/react-rails'
2727
```
2828

2929
## Usage
@@ -35,7 +35,7 @@ In order to use React client-side in your application, you must make sure the br
3535
You can `require` it in your manifest:
3636

3737
```js
38-
// app/assets/application.js
38+
// app/assets/javascripts/application.js
3939

4040
//= require react
4141
```
@@ -69,15 +69,15 @@ Component = React.createClass
6969

7070
```erb
7171
<!-- react_ujs will execute `React.renderComponent(HelloMessage({name:"Bob"}), element)` -->
72-
<div data-react-class="HelloMessage" data-react-props="<%= {:name => 'Bob'}.to_json %>" />
72+
<div data-react-class="HelloMessage" data-react-props="<%= {name: 'Bob'}.to_json %>" />
7373
```
7474

7575
`react_ujs` will also scan DOM elements and call `React.unmountComponentAtNode` on page unload. If you want to disable this behavior, remove `data-react-class` attribute in `componentDidMount`.
7676

7777
To use `react_ujs`, simply `require` it after `react` (and after `turbolinks` if [Turbolinks](https://github.com/rails/turbolinks) is used):
7878

7979
```js
80-
// app/assets/application.js
80+
// app/assets/javascripts/application.js
8181

8282
//= require turbolinks
8383
//= require react
@@ -89,17 +89,17 @@ To use `react_ujs`, simply `require` it after `react` (and after `turbolinks` if
8989
There is a view helper method `react_component`. It is designed to work with `react_ujs` and takes a React class name, properties, and HTML options as arguments:
9090

9191
```ruby
92-
react_component('HelloMessage', :name => 'John')
92+
react_component('HelloMessage', name: 'John')
9393
# <div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}"></div>
9494
```
9595

9696
By default, a `<div>` element is used. Other tag and HTML attributes can be specified:
9797

9898
```ruby
99-
react_component('HelloMessage', {:name => 'John'}, :span)
99+
react_component('HelloMessage', {name: 'John'}, :span)
100100
# <span data-...></span>
101101

102-
react_component('HelloMessage', {:name => 'John'}, {:id => 'hello', :class => 'foo', :tag => :span})
102+
react_component('HelloMessage', {name: 'John'}, {id: 'hello', class: 'foo', tag: :span})
103103
# <span class="foo" id="hello" data-...></span>
104104
```
105105

@@ -118,14 +118,14 @@ gem "therubyrhino", :platforms => :jruby
118118

119119
#### components.js
120120

121-
In order for us to render your React components, we need to be able to find them and load them into the JS VM. By convention, we look for a `assets/components.js` file through the asset pipeline, and load that. For example:
121+
In order for us to render your React components, we need to be able to find them and load them into the JS VM. By convention, we look for a `assets/javascripts/components.js` file through the asset pipeline, and load that. For example:
122122

123123
```sass
124124
// app/assets/javascripts/components.js
125125
//= require_tree ./components
126126
```
127127

128-
This will bring in all files located in the `app/assets/javascripts/components` directory. You can organize your code however you like, as long as a request for `/assets/components.js` brings in a concatenated file containing all of your React components, and each one has to be available in the global scope (either `window` or `global` can be used). For `.js.jsx` files this is not a problem, but if you are using `.js.jsx.coffee` files then the wrapper function needs to be taken into account:
128+
This will bring in all files located in the `app/assets/javascripts/components` directory. You can organize your code however you like, as long as a request for `/assets/javascripts/components.js` brings in a concatenated file containing all of your React components, and each one has to be available in the global scope (either `window` or `global` can be used). For `.js.jsx` files this is not a problem, but if you are using `.js.jsx.coffee` files then the wrapper function needs to be taken into account:
129129

130130
```coffee
131131
###* @jsx React.DOM ###
@@ -139,10 +139,10 @@ window.Component = Component
139139

140140
#### View Helper
141141

142-
To take advantage of server rendering, use the same view helper `react_component`, and pass in `:prerender => true` in the `options` hash.
142+
To take advantage of server rendering, use the same view helper `react_component`, and pass in `prerender: true` in the `options` hash.
143143

144144
```erb
145-
react_component('HelloMessage', {:name => 'John'}, {:prerender => true})
145+
react_component('HelloMessage', {name: 'John'}, {prerender: true})
146146
```
147147
This will return the fully rendered component markup, and as long as you have included the `react_ujs` script in your page, then the component will also be instantiated and mounted on the client.
148148

lib/react/rails/railtie.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ class Railtie < ::Rails::Railtie
1919
app.config.watchable_files.concat Dir["#{app.root}/app/assets/javascripts/**/*.jsx*"]
2020
end
2121

22+
# Include the react-rails view helper lazily
23+
initializer "react_rails.setup_view_helpers" do
24+
ActiveSupport.on_load(:action_view) do
25+
include ::React::Rails::ViewHelper
26+
end
27+
end
28+
2229
# run after all initializers to allow sprockets to pick up react.js and
2330
# jsxtransformer.js from end-user to override ours if needed
2431
initializer "react_rails.setup_vendor", :after => "sprockets.environment", group: :all do |app|
@@ -56,13 +63,15 @@ class Railtie < ::Rails::Railtie
5663
config.after_initialize do |app|
5764
# Server Rendering
5865
# Concat component_filenames together for server rendering
59-
app.config.react.components_js = app.config.react.component_filenames.map do |filename|
60-
app.assets[filename].to_s
61-
end.join(";")
66+
app.config.react.components_js = lambda {
67+
app.config.react.component_filenames.map do |filename|
68+
app.assets[filename].to_s
69+
end.join(";")
70+
}
6271

6372
do_setup = lambda do
6473
cfg = app.config.react
65-
React::Renderer.setup!( cfg.react_js.call, cfg.components_js,
74+
React::Renderer.setup!( cfg.react_js, cfg.components_js,
6675
{:size => cfg.size, :timeout => cfg.timeout})
6776
end
6877

lib/react/rails/view_helper.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,3 @@ def react_component(name, args = {}, options = {}, &block)
2323
end
2424
end
2525
end
26-
27-
ActionView::Base.class_eval do
28-
include ::React::Rails::ViewHelper
29-
end

lib/react/renderer.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def self.setup!(react_js, components_js, args={})
1010
@@react_js = react_js
1111
@@components_js = components_js
1212
@@pool.shutdown{} if @@pool
13+
reset_combined_js!
1314
@@pool = ConnectionPool.new(:size => args[:size]||10, :timeout => args[:timeout]||20) { self.new }
1415
end
1516

@@ -19,8 +20,8 @@ def self.render(component, args={})
1920
end
2021
end
2122

22-
def self.combined_js
23-
@@combined_js ||= <<-CODE
23+
def self.setup_combined_js
24+
<<-CODE
2425
var global = global || this;
2526
2627
var console = global.console || {};
@@ -30,12 +31,20 @@ def self.combined_js
3031
}
3132
});
3233
33-
#{@@react_js};
34+
#{@@react_js.call};
3435
React = global.React;
35-
#{@@components_js};
36+
#{@@components_js.call};
3637
CODE
3738
end
3839

40+
def self.reset_combined_js!
41+
@@combined_js = setup_combined_js
42+
end
43+
44+
def self.combined_js
45+
@@combined_js
46+
end
47+
3948
def context
4049
@context ||= ExecJS.compile(self.class.combined_js)
4150
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/** @jsx React.DOM */
2+
3+
TodoList = React.createClass({
4+
getInitialState: function() {
5+
return({mounted: "nope"});
6+
},
7+
componentWillMount: function() {
8+
this.setState({mounted: 'yep'});
9+
},
10+
render: function() {
11+
return (
12+
<ul>
13+
<li>Updated</li>
14+
<li id='status'>{this.state.mounted}</li>
15+
{this.props.todos.map(function(todo, i) {
16+
return (<Todo key={i} todo={todo} />)
17+
})}
18+
</ul>
19+
)
20+
}
21+
})
22+

test/react_test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class ReactTest < ActionDispatch::IntegrationTest
3131

3232
assert_response :success
3333
assert_equal "'test_confirmation_token_react_content';\n", @response.body
34+
35+
React::Renderer.reset_combined_js!
3436
end
3537

3638
end

test/server_rendered_html_test.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
require 'test_helper'
2+
require 'fileutils'
3+
4+
class ServerRenderedHtmlTest < ActionDispatch::IntegrationTest
5+
# Rails' asset pipeline has trouble picking up changes to files if they happen too fast.
6+
# By sleeping for a little bit at certain points, we can make sure that rails notices the
7+
# change in the file mtime, and calls our renderer setup functions appropriately
8+
def wait_to_ensure_asset_pipeline_detects_changes
9+
sleep(1)
10+
end
11+
12+
test 'react server rendering reloads jsx after changes to the jsx files' do
13+
file_with_updates = File.expand_path('../helper_files/TodoListWithUpdates.js.jsx', __FILE__)
14+
file_without_updates = File.expand_path('../helper_files/TodoListWithoutUpdates.js.jsx', __FILE__)
15+
app_file = File.expand_path('../dummy/app/assets/javascripts/components/TodoList.js.jsx', __FILE__)
16+
17+
FileUtils.cp app_file, file_without_updates
18+
FileUtils.touch app_file
19+
20+
begin
21+
get '/server/1'
22+
refute_match 'Updated', response.body
23+
24+
wait_to_ensure_asset_pipeline_detects_changes
25+
FileUtils.cp file_with_updates, app_file
26+
FileUtils.touch app_file
27+
28+
get '/server/1'
29+
assert_match 'Updated', response.body
30+
ensure
31+
# if we have a test failure, we want to make sure that we revert the dummy file
32+
wait_to_ensure_asset_pipeline_detects_changes
33+
FileUtils.mv file_without_updates, app_file
34+
FileUtils.touch app_file
35+
end
36+
end
37+
end

test/test_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,7 @@
2323
if ActiveSupport::TestCase.method_defined?(:fixture_path=)
2424
ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
2525
end
26+
27+
def wait_for_turbolinks_to_be_available
28+
sleep(1)
29+
end

test/view_helper_test.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@ class ViewHelperTest < ActionDispatch::IntegrationTest
6262
page.execute_script('history.back();')
6363
assert page.has_content?('Hello Alice')
6464

65+
wait_for_turbolinks_to_be_available()
66+
6567
# Try Turbolinks javascript API.
6668
page.execute_script('Turbolinks.visit("/pages/2");')
6769
assert page.has_content?('Hello Alice')
6870

71+
wait_for_turbolinks_to_be_available()
72+
6973
page.execute_script('Turbolinks.visit("/pages/1");')
7074
assert page.has_content?('Hello Bob')
7175

0 commit comments

Comments
 (0)