# Rack::Component Like a React.js component, a `Rack::Component` implements a `render` method that takes input data and returns what to display. You can use Components instead of Controllers, Views, Templates, and Helpers, in any Rack app. ## Install Add `rack-component` to your Gemfile and run `bundle install`: ``` gem 'rack-component' ``` ## Quickstart with Sinatra ```ruby # config.ru require 'sinatra' require 'rack/component' class Hello < Rack::Component render do |env| "

Hello, #{h env[:name]}

" end end get '/hello/:name' do Hello.call(name: params[:name]) end run Sinatra::Application ``` **Note that Rack::Component does not escape strings by default**. To escape strings, you can either use the `#h` helper like in the example above, or you can configure your components to render a template that escapes automatically. See the [Recipes](#recipes) section for details. ## Table of Contents * [Getting Started](#getting-started) * [Components as plain functions](#components-as-plain-functions) * [Components as Rack::Components](#components-as-rackcomponents) * [Components if you hate inheritance](#components-if-you-hate-inheritance) * [Recipes](#recipes) * [Render one component inside another](#render-one-component-inside-another) * [Render a template that escapes output by default via Tilt](#render-a-template-that-escapes-output-by-default-via-tilt) * [Render an HTML list from an array](#render-an-html-list-from-an-array) * [Render a Rack::Component from a Rails controller](#render-a-rackcomponent-from-a-rails-controller) * [Mount a Rack::Component as a Rack app](#mount-a-rackcomponent-as-a-rack-app) * [Build an entire App out of Rack::Components](#build-an-entire-app-out-of-rackcomponents) * [Define `#render` at the instance level instead of via `render do`](#define-render-at-the-instance-level-instead-of-via-render-do) * [API Reference](#api-reference) * [Performance](#performance) * [Compatibility](#compatibility) * [Anybody using this in production?](#anybody-using-this-in-production) * [Ruby reference](#ruby-reference) * [Development](#development) * [Contributing](#contributing) * [License](#license) ## Getting Started ### Components as plain functions The simplest component is just a lambda that takes an `env` parameter: ```ruby Greeter = lambda do |env| "

Hi, #{env[:name]}.

" end Greeter.call(name: 'Mina') #=> '

Hi, Mina.

' ``` ### Components as Rack::Components Upgrade your lambda to a `Rack::Component` when it needs HTML escaping, instance methods, or state: ```ruby require 'rack/component' class FormalGreeter < Rack::Component render do |env| "

Hi, #{h title} #{h env[:name]}.

" end # +env+ is available in instance methods too def title env[:title] || "Queen" end end FormalGreeter.call(name: 'Franklin') #=> "

Hi, Queen Franklin.

" FormalGreeter.call( title: 'Captain', name: 'Kirk ' ) #=>

Hi, Captain Kirk <kirk@starfleet.gov>.

``` #### Components if you hate inheritance Instead of inheriting from `Rack::Component`, you can `extend` its methods: ```ruby class SoloComponent extend Rack::Component::Methods render { "Family is complicated" } end ``` ## Recipes ### Render one component inside another You can nest Rack::Components as if they were [React Children][jsx children] by calling them with a block. ```ruby Layout.call(title: 'Home') do Content.call end ``` Here's a more fully fleshed example: ```ruby require 'rack/component' # let's say this is a Sinatra app: get '/posts/:id' do PostPage.call(id: params[:id]) end # Fetch a post from the database and render it inside a Layout class PostPage < Rack::Component render do |env| post = Post.find env[:id] # Nest a PostContent instance inside a Layout instance, # with some arbitrary HTML too Layout.call(title: post.title) do <<~HTML
#{PostContent.call(title: post.title, body: post.body)}
I am a footer.
HTML end end end class Layout < Rack::Component # The +render+ macro supports Ruby's keyword arguments, and, like any other # Ruby function, can accept a block via the & operator. # Here, :title is a required key in +env+, and &child is just a regular Ruby # block that could be named anything. render do |title:, **, &child| <<~HTML #{h title} #{child.call} HTML end end class PostContent < Rack::Component render do |title:, body:, **| <<~HTML

#{h title}

#{h body}
HTML end end ``` ### Render a template that escapes output by default via Tilt If you add [Tilt][tilt] and `erubi` to your Gemfile, you can use the `render` macro with an automatically-escaped template instead of a block. ```ruby # Gemfile gem 'tilt' gem 'erubi' gem 'rack-component' # my_component.rb class TemplateComponent < Rack::Component render erb: <<~ERB

Hello, <%= name %>

ERB def name env[:name] || 'Someone' end end TemplateComponent.call #=>

Hello, Someone

TemplateComponent.call(name: 'Spock<>') #=>

Hello, Spock<>

``` Rack::Component passes `{ escape_html: true }` to Tilt by default, which enables automatic escaping in ERB (via erubi) Haml, and Markdown. To disable automatic escaping, or to pass other tilt options, use an `opts: {}` key in `render`: ```ruby class OptionsComponent < Rack::Component render opts: { escape_html: false, trim: false }, erb: <<~ERB
Hi there, <%= {env[:name] %> <%== yield %>
ERB end ``` Template components support using the `yield` keyword to render child components, but note the double-equals `<%==` in the example above. If your component escapes HTML, and you're yielding to a component that renders HTML, you probably want to disable escaping via `==`, just for the `<%== yield %>` call. This is safe, as long as the component you're yielding to uses escaping. Using `erb` as a key for the inline template is a shorthand, which also works with `haml` and `markdown`. But you can also specify `engine` and `template` explicitly. ```ruby require 'haml' class HamlComponent < Rack::Component # Note the special HEREDOC syntax for inline Haml templates! Without the # single-quotes, Ruby will interpret #{strings} before Haml does. render engine: 'haml', template: <<~'HAML' %h1 Hi #{env[:name]}. HAML end ``` Using a template instead of raw string interpolation is a safer default, but it can make it less convenient to do logic while rendering. Feel free to override your Component's `#initialize` method and do logic there: ```ruby class EscapedPostView < Rack::Component def initialize(env) @post = Post.find(env[:id]) # calling `super` will populate the instance-level `env` hash, making # `env` available outside this method. But it's fine to skip it. super end render erb: <<~ERB

<%= @post.title %>

<%= @post.body %>
ERB end ``` ### Render an HTML list from an array [JSX Lists][jsx lists] use JavaScript's `map` function. Rack::Component does likewise, only you need to call `join` on the array: ```ruby require 'rack/component' class PostsList < Rack::Component render do <<~HTML

This is a list of posts

HTML end def render_items env[:posts].map { |post| <<~HTML
  • #{post[:name]}
  • HTML }.join # unlike JSX, you need to call `join` on your array end end posts = [{ name: 'First Post', id: 1 }, { name: 'Second', id: 2 }] PostsList.call(posts: posts) #=>

    This is a list of posts