diff --git a/README.md b/README.md index 4aed2e790..6acd40534 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,18 @@ react_component('HelloMessage', {name: 'John'}, {id: 'hello', class: 'foo', tag: # ``` +### Controller helper + +There is also a controller helper `render_react_component` which is included so that you can specify a component as your view instead of an extra view file that only uses the `react_component` view helper. + +```ruby +class TodoController < ApplicationController + def show + todos = Todos.all + render_react_component 'TodoList', { todos: todos }, prerender: true + end +``` + #### With JSON and Jbuilder You can pass prepared JSON directly to the helper, as well. diff --git a/lib/react/rails.rb b/lib/react/rails.rb index 18466040c..a7db02c73 100644 --- a/lib/react/rails.rb +++ b/lib/react/rails.rb @@ -1,3 +1,4 @@ require 'react/rails/railtie' require 'react/rails/engine' +require 'react/rails/controller_helper' require 'react/rails/view_helper' diff --git a/lib/react/rails/controller_helper.rb b/lib/react/rails/controller_helper.rb new file mode 100644 index 000000000..3573640af --- /dev/null +++ b/lib/react/rails/controller_helper.rb @@ -0,0 +1,26 @@ +module React + module Rails + module ControllerHelper + + # Render a React component as the view instead of + # a template from the app/views folder. + # + def render_react_component(name, args = {}, options = {}, &block) + __render_react_component_args = { + inline: '<%= react_component(*@__react_component_args, &@__react_component_block) %>', + layout: options.delete(:layout) || _layout.virtual_path + } + + if status = options.delete(:status) + __render_react_component_args.merge!(status: status) + end + + @__react_component_args = [name, args, options] + @__react_component_block = block + + render __render_react_component_args + end + + end + end +end diff --git a/lib/react/rails/railtie.rb b/lib/react/rails/railtie.rb index ad287f41d..dc6208b29 100644 --- a/lib/react/rails/railtie.rb +++ b/lib/react/rails/railtie.rb @@ -26,6 +26,13 @@ class Railtie < ::Rails::Railtie end end + # Include the react-rails controller helper lazily + initializer "react_rails.setup_controller_helpers" do + ActiveSupport.on_load(:action_controller) do + include ::React::Rails::ControllerHelper + end + end + # run after all initializers to allow sprockets to pick up react.js and # jsxtransformer.js from end-user to override ours if needed initializer "react_rails.setup_vendor", :after => "sprockets.environment", group: :all do |app| diff --git a/test/controller_helper_test.rb b/test/controller_helper_test.rb new file mode 100644 index 000000000..6fcd254c7 --- /dev/null +++ b/test/controller_helper_test.rb @@ -0,0 +1,26 @@ +require 'test_helper' +require 'support/setup_capybara' + +class ControllerHelperTest < ActionDispatch::IntegrationTest + include Capybara::DSL + + setup do + @helper = ActionView::Base.new.extend(React::Rails::ViewHelper) + Capybara.current_driver = Capybara.javascript_driver + end + + test 'uses a custom layout and status' do + get '/helper/1' + + assert response.status == 218 + assert response.body.include?('This is a different layout') + end + + test 'renders the React component' do + get '/helper/1' + + %w(data-react-class="Foo" data-react-props="{"bar":"value"}").each do |segment| + assert response.body.include?(segment) + end + end +end diff --git a/test/dummy/app/controllers/helper_controller.rb b/test/dummy/app/controllers/helper_controller.rb new file mode 100644 index 000000000..94e4abd58 --- /dev/null +++ b/test/dummy/app/controllers/helper_controller.rb @@ -0,0 +1,6 @@ +class HelperController < ApplicationController + def show + @todos = %w{todo1 todo2 todo3} + render_react_component "Foo", {:bar => 'value'}, layout: 'custom', status: 218 + end +end diff --git a/test/dummy/app/views/layouts/custom.html.erb b/test/dummy/app/views/layouts/custom.html.erb new file mode 100644 index 000000000..5b4771bd4 --- /dev/null +++ b/test/dummy/app/views/layouts/custom.html.erb @@ -0,0 +1,15 @@ + + + + Dummy + <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> + <%= javascript_include_tag "application", "data-turbolinks-track" => true %> + <%= csrf_meta_tags %> + + + +This is a different layout +<%= yield %> + + + diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index daf2e2b04..03ecbd818 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,4 +1,5 @@ Dummy::Application.routes.draw do resources :pages, :only => [:show] resources :server, :only => [:show] + resources :helper, :only => [:show] end diff --git a/test/support/setup_capybara.rb b/test/support/setup_capybara.rb new file mode 100644 index 000000000..b4ee69966 --- /dev/null +++ b/test/support/setup_capybara.rb @@ -0,0 +1,13 @@ +require 'capybara/rails' +require 'capybara/poltergeist' + +Capybara.javascript_driver = :poltergeist +Capybara.app = Rails.application + +# Useful for debugging. +# Just put page.driver.debug in your test and it will +# pause and throw up a browser +Capybara.register_driver :poltergeist_debug do |app| + Capybara::Poltergeist::Driver.new(app, :inspector => true) +end +Capybara.javascript_driver = :poltergeist_debug diff --git a/test/view_helper_test.rb b/test/view_helper_test.rb index f9e3166e7..dc1069b4d 100644 --- a/test/view_helper_test.rb +++ b/test/view_helper_test.rb @@ -1,18 +1,5 @@ require 'test_helper' - -require 'capybara/rails' -require 'capybara/poltergeist' - -Capybara.javascript_driver = :poltergeist -Capybara.app = Rails.application - -# Useful for debugging. -# Just put page.driver.debug in your test and it will -# pause and throw up a browser -Capybara.register_driver :poltergeist_debug do |app| - Capybara::Poltergeist::Driver.new(app, :inspector => true) -end -Capybara.javascript_driver = :poltergeist_debug +require 'support/setup_capybara' class ViewHelperTest < ActionDispatch::IntegrationTest include Capybara::DSL