<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Naofumi Kagami</title>
    <description>The latest articles on DEV Community by Naofumi Kagami (@naofumik).</description>
    <link>https://dev.to/naofumik</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1756386%2F193bcb87-55cd-442f-94c1-6d2b9cb30558.jpeg</url>
      <title>DEV Community: Naofumi Kagami</title>
      <link>https://dev.to/naofumik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/naofumik"/>
    <language>en</language>
    <item>
      <title>Integrating Ruby on Rails with Modern SPAs</title>
      <dc:creator>Naofumi Kagami</dc:creator>
      <pubDate>Tue, 22 Apr 2025 12:52:53 +0000</pubDate>
      <link>https://dev.to/naofumik/integrating-ruby-on-rails-with-modern-spas-hnk</link>
      <guid>https://dev.to/naofumik/integrating-ruby-on-rails-with-modern-spas-hnk</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR;
&lt;/h2&gt;

&lt;p&gt;Integrating React onto a Ruby on Rails application is unnecessarily challenging. We have to install jsbundling-rails, multiple packages, configure propshaft, create an ERB endpoint, etc.&lt;/p&gt;

&lt;p&gt;Why can't we just run &lt;code&gt;rails new&lt;/code&gt;, install a single gem, and instantly have a state-of-the-art React setup with client-side routing, code-splitting, and effective data-loading patterns built-in?&lt;/p&gt;

&lt;p&gt;This article introduces the &lt;a href="https://github.com/naofumi/react_router_rails_spa" rel="noopener noreferrer"&gt;react_router_rails_spa gem&lt;/a&gt;, which integrates a React Router SPA framework application into your existing Ruby on Rails project. With just a few commands, you will have a fully functioning scaffold on which to build your React app.&lt;/p&gt;

&lt;p&gt;To jump to the installation steps, go to the "Using the gem" section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;p&gt;If any of the following describes yourself, then this gem might be for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  You want to create an SPA with a React frontend and a Rails backend.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want a simple setup that is &lt;a href="https://dhh.dk/2012/rails-is-omakase.html" rel="noopener noreferrer"&gt;"omakase"&lt;/a&gt;. You don't want to install packages and configure them on the React side. On the Rails side, you don't want to manually add routes and controllers. Everything should be a single gem and a single command.&lt;/li&gt;
&lt;li&gt;You want something that is easy to deploy, and cost-effective. You don't want to pay for an extra server that you don't strictly need.&lt;/li&gt;
&lt;li&gt;You don't need SEO for the React pages. &lt;/li&gt;
&lt;li&gt;If SEO is necessary, you can just serve ERB pages or static HTML files. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  You tried Next.js, but you did not really need SSR nor RSCs. You were only interested in Next.js because you thought it was easy.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You're only using Next.js because you thought it was &lt;a href="https://dhh.dk/2012/rails-is-omakase.html" rel="noopener noreferrer"&gt;"omakase"&lt;/a&gt; and simple to set up.&lt;/li&gt;
&lt;li&gt;After a while, you've found that integrating the Next.js server and the Rails server is harder than you bargained for. You've had headaches around authentication schemes, cross-domains, subdomains, CORS settings, samesite cookies, CSRF mitigation, and reverse proxies, etc. You've realized that running separate frontend and backend servers is hard.&lt;/li&gt;
&lt;li&gt;You are not happy with the extra money you have to pay to Next.js.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/naofumi/react_router_rails_spa" rel="noopener noreferrer"&gt;react_router_rails_spa gem&lt;/a&gt; gives you the simplicity of an opinionated SPA framework, without the complexity of a mulit-server setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  You want to use cutting-edge React
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want to use cutting-edge React capabilities like code-splitting, loader-based data fetching and more, but you're not sure how to &lt;a href="https://react.dev/learn/build-a-react-app-from-scratch" rel="noopener noreferrer"&gt;set this up from scratch&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You don't want to create a slow, bloated, legacy React app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who is this NOT for?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  You want to embed some React components on top of your ERB-rendered pages
&lt;/h3&gt;

&lt;p&gt;This is &lt;a href="https://react.dev/learn/add-react-to-an-existing-project#using-react-for-a-part-of-your-existing-page" rel="noopener noreferrer"&gt;how React was originally used&lt;/a&gt;. React was built for this, and it's generally much simpler to set up than a multi-page SPA.&lt;/p&gt;

&lt;p&gt;The current gem does not help you with this however since it is designed for SPA frameworks. If you wish to take this approach, you can build your own system or use Gems like &lt;a href="https://github.com/reactjs/react-rails" rel="noopener noreferrer"&gt;react-rails&lt;/a&gt; and  &lt;a href="https://github.com/skryukov/turbo-mount" rel="noopener noreferrer"&gt;turbo-mount&lt;/a&gt;. Turbo Mount uses Stimulus to mount components, and is more robust if you are using Hotwire in your ERB views.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does this compare to [...]?
&lt;/h2&gt;

&lt;p&gt;Compared to gems like &lt;a href="https://github.com/shakacode/react_on_rails" rel="noopener noreferrer"&gt;React on Rails&lt;/a&gt; or &lt;a href="https://inertia-rails.dev/" rel="noopener noreferrer"&gt;Intertia Rails&lt;/a&gt;, the current gem is just an installer and does virtually nothing to modify or add features to React Router. This is a great advantage and ensures that frontend developers will feel right at home.&lt;/p&gt;

&lt;p&gt;It also means that you can easily understand how it works. You can customize accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;On February 14th, 2025, the React team published a blog post&lt;br&gt;
titled &lt;a href="https://react.dev/blog/2025/02/14/sunsetting-create-react-app" rel="noopener noreferrer"&gt;Sunsetting Create React App&lt;/a&gt;. They strongly recommended that &lt;strong&gt;developers should now use an SPA framework instead&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Importantly, and often lost in the public discourse, they were &lt;strong&gt;NOT&lt;/strong&gt; recommending an &lt;strong&gt;SSR&lt;/strong&gt; framework. Instead, they were advocating for creating SPAs with &lt;a href="https://react.dev/blog/2025/02/14/sunsetting-create-react-app#how-to-migrate-to-a-framework" rel="noopener noreferrer"&gt;&lt;strong&gt;SPA&lt;/strong&gt; frameworks&lt;/a&gt; that could be deployed on a CDN, a static hosting service, or the &lt;code&gt;public&lt;/code&gt; folder of a Ruby on Rails application.&lt;/p&gt;

&lt;p&gt;In the following, we will call SPAs built with an SPA framework, &lt;strong&gt;"Modern React SPAs"&lt;/strong&gt; to highlight that this is the current officially recommended approach. To contrast, we will call the ones that the React team is actively discouraging, &lt;strong&gt;"Legacy React SPAs"&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"I have no interest nor use for SSR! I don't need SEO. An SPA is all that I need.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instead of Create React App, I'll just use Vite!"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was the most common response to the blog post. However IMO, it misses the point that the authors were repeatedly trying to make.&lt;/p&gt;

&lt;p&gt;The React team is strongly recommending that &lt;strong&gt;even if you only need an SPA, you should be creating a Modern SPA&lt;/strong&gt;. &lt;a href="https://react.dev/learn/build-a-react-app-from-scratch" rel="noopener noreferrer"&gt;They carefully go through some of the features&lt;/a&gt; like code-splitting and loader-based routing that you will need to add if you are building a state-of-the-art React SPA from scratch.&lt;/p&gt;

&lt;p&gt;These features are often challenging and require expertise to correctly implement, but are essential for modern React applications.&lt;/p&gt;

&lt;p&gt;Without these features, your React SPA is a Legacy SPA. It will suffer from the same performance issues that plagued old SPAs a decade ago – namely huge initial bundle size, data-fetch waterfalls, flickering, and very slow load times.&lt;/p&gt;

&lt;p&gt;In the above article, the React team went out of its way to tell us that we should not simply replace the deprecated Create React App with a newer but nonetheless still architecturally Legacy SPA. Instead, they strongly urge us to embrace Modern React SPAs and avoid these issues.&lt;/p&gt;

&lt;p&gt;We should note that Vite is essentially a bundler and a development server, with a plugin system that allows us to easily install various packages. Although extremely useful, it is agnostic to the Legacy vs. Modern SPA debate. You can build a Legacy SPA using Vite, and you can also create a Modern SPA. Vite does not care either way, and the installer command &lt;code&gt;npm create vite@latest&lt;/code&gt; gives you templates for both.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"I want to integrate my React app with a Ruby on Rails backend. I'll just add a &lt;code&gt;javascript_include_tag&lt;/code&gt; to my bootstrap ERB template. That will load React just fine!"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is also a common response from people who prefer a no-fuss solution for Rails. However, note that this is exactly the approach that the React team strongly discourages. The above solution is a Legacy SPA and will suffer from the same legacy issues.&lt;/p&gt;

&lt;p&gt;Instead, the React team is recommending that you integrate a Modern SPA framework using ...&lt;/p&gt;

&lt;p&gt;Well, actually, they don't have a concrete recommendation yet for Rails. As far as we know, nothing currently exists to easily integrate a Modern SPA with Rails.&lt;/p&gt;

&lt;p&gt;We hope to address this with this &lt;a href="https://github.com/naofumi/react_router_rails_spa" rel="noopener noreferrer"&gt;react_router_rails_spa gem&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why we need a different approach for Rails integration
&lt;/h2&gt;

&lt;p&gt;Historically, the way to integrate React with Ruby on Rails was to create an ERB endpoint that served as the initial HTML&lt;br&gt;
(the bootstrap HTML) for React. This ERB template would have either a &lt;code&gt;javascript_include_tag&lt;/code&gt; (jsbundling-rails) or a &lt;code&gt;javascript_pack_tag&lt;/code&gt; (webpacker) to load the React application build artifact. Newer gems like &lt;a href="https://vite-ruby.netlify.app/guide/rails.html#tag-helpers-%F0%9F%8F%B7" rel="noopener noreferrer"&gt;Vite Rails&lt;/a&gt; have also adopted the same approach.&lt;/p&gt;

&lt;p&gt;However, this is exactly what the React team is discouraging, and it seems unwise to continue down this path.&lt;/p&gt;

&lt;p&gt;The problem is that Modern SPAs build their own bootstrap HTML templates with SSG (the first HTML that the browser loads).&lt;br&gt;
Modern SPA frameworks are not just JavaScript and instead, the bootstrap HTML and the JavaScript are tightly integrated.&lt;/p&gt;

&lt;p&gt;Therefore, to take advantage of Modern SPA features, Rails has to give up on generating its own bootstrap HTML from an ERB template with an embedded &lt;code&gt;javascript_include_tag&lt;/code&gt;. A better approach is to take the bootstrap HTML generated by the SPA framework, and wrap Rails integration around this.&lt;/p&gt;

&lt;p&gt;This is how the react_router_rails_spa gem works.&lt;/p&gt;
&lt;h2&gt;
  
  
  Outline of how the gem works
&lt;/h2&gt;

&lt;p&gt;It is important to note that the &lt;a href="https://github.com/naofumi/react_router_rails_spa" rel="noopener noreferrer"&gt;react_router_rails_spa gem&lt;/a&gt; is nothing more than a stock React Router installation with some custom configuration, paired with the generation of a single Rails controller.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is very little custom code, and this is actually a huge advantage&lt;/strong&gt;. This is a very thin wrapper around the official React Router installer and resilient against future changes. If you wish, you can easily update and customise your NPM packages independently of this gem. The generated code is also heavily commented&lt;br&gt;
to help you understand the internals for yourself.&lt;/p&gt;
&lt;h3&gt;
  
  
  React Router SPA framework mode
&lt;/h3&gt;

&lt;p&gt;We install and use React Router in SPA framework mode, &lt;a href="https://reactrouter.com/how-to/spa" rel="noopener noreferrer"&gt;configured to generate an SPA build&lt;/a&gt;.&lt;br&gt;
It is a true SPA and will build static files that can be served from any static hosting provider. These are transferred to the &lt;code&gt;public&lt;/code&gt; folder in Rails for deployment.&lt;/p&gt;

&lt;p&gt;The command for building the react application is integrated into the &lt;code&gt;rake assets:precompile&lt;/code&gt; command. Therefore, you do not need any additional configuration in your CI/CD scripts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Integration with Ruby on Rails through cookies
&lt;/h3&gt;

&lt;p&gt;Previously, you would integrate Ruby on Rails using a bootstrap HTML template generated by Rails using ERB templates. This allowed you, for example, to embed CSRF-mitigation token tags. As mentioned above, this is incompatible with Modern SPA frameworks.&lt;/p&gt;

&lt;p&gt;Otherwise, you could build your SPA independently of Rails and send extra requests from the browser to retrieve the CSRF token and other integration information. This requires otherwise unnecessary network requests.&lt;/p&gt;

&lt;p&gt;Neither approach is ideal.&lt;/p&gt;

&lt;p&gt;Instead, the &lt;a href="https://github.com/naofumi/react_router_rails_spa" rel="noopener noreferrer"&gt;react_router_rails_spa gem&lt;/a&gt; sends Rails-generated cookies and/or headers alongside the SPA framework-generated bootstrap HTML file. It give you the best of both worlds.&lt;/p&gt;

&lt;p&gt;For example, the &lt;code&gt;ReactRouterRailsSpa::CsrfCookieEnabled&lt;/code&gt; module&lt;br&gt;
sends session-specific CSRF tokens via cookies to integrate Rails'&lt;br&gt;
CSRF protection with React. The SPA framework-generated HTML is untouched.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rake files for automation
&lt;/h3&gt;

&lt;p&gt;We provide rake tasks for starting up the development server and building the React Router application.&lt;/p&gt;

&lt;p&gt;Note that the build task is attached to the &lt;code&gt;assets:precompile&lt;/code&gt; task.&lt;br&gt;
This means that you do not need to add extra configuration to your CI/CD scripts to build the React Router app since it should normally call this task already.&lt;/p&gt;

&lt;p&gt;If your CI/CD already installs Node (which is required for building), then you probably won't have to touch your CI/CD scripts at all.&lt;/p&gt;
&lt;h3&gt;
  
  
  Additional React Router and Vite Configuration
&lt;/h3&gt;

&lt;p&gt;We currently serve the React application from the &lt;code&gt;/react/*&lt;/code&gt; paths. All other paths are handled by Rails. The current gem configures this for you. If you want a different setup, you can change the configurations.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo and Source code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We have a &lt;a href="https://rrrails.castle104.com/react/" rel="noopener noreferrer"&gt;demo application running on Kamal on a VPS server&lt;/a&gt;. It has simple, session-based authentication and basic CRUD. Mutations are secured by integration with Rails CSRF protection.&lt;/li&gt;
&lt;li&gt;In the demo application, we have intentionally added a 0.5 to 1.5-second random delay on all server requests. Even the most bloated and inefficient web technologies will look great on a high-performance device with a fast network. Unless your demo intentionally simulates non-ideal situations, it is meaningless.&lt;/li&gt;
&lt;li&gt;The source-code for this demo application is &lt;a href="https://github.com/naofumi/react-router-vite-rails" rel="noopener noreferrer"&gt;available on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source code is heavily commented. We recommend that you read through it to understand the setup in more detail.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using the gem
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Install Ruby on Rails
&lt;/h3&gt;

&lt;p&gt;This gem works with a pre-existing installation of Rails. Create a new Rails application if you haven't already.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new &lt;span class="o"&gt;[&lt;/span&gt;project name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this gem works even with a no-build Rails setup (which is the Rails default). However, you will need Node.js in your CI/CD for deployment. If you are unsure how to do this, we recommend that you generate a new Rails application with jsbundling-rails pre-installed by using the following installation command.&lt;br&gt;
This will create a ready-made Dockerfile that installs Node.js.&lt;br&gt;
(This gem won't use esbuild. We're only doing this for Node.js.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new &lt;span class="o"&gt;[&lt;/span&gt;project name] &lt;span class="nt"&gt;--javascript&lt;/span&gt; esbuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install the &lt;code&gt;react_router_rails_spa&lt;/code&gt; gem
&lt;/h3&gt;

&lt;p&gt;Add the following line to your &lt;code&gt;Gemfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"react_router_rails_spa"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the gem&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We recommend committing your changes at this point before the following generator adds and modifies your files.&lt;/p&gt;

&lt;p&gt;Run the generator&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails generate react_router_rails_spa:install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install the latest version of React Router and generate the routes and all the necessary files and configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run the development server
&lt;/h3&gt;

&lt;p&gt;Run the following command to start the frontend development server with HMR (Hot Module Replacement).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails react_router:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point your browser to &lt;a href="http://localhost:5173/react/" rel="noopener noreferrer"&gt;http://localhost:5173/react/&lt;/a&gt; to see the welcome page.&lt;/p&gt;

&lt;p&gt;Note that the frontend development server will not truly represent&lt;br&gt;
how the React application and Rails server interact in production. In particular, features that require integration like linking between the React and Rails app may not work. It is important to test with the following preview command before deploying.&lt;/p&gt;
&lt;h3&gt;
  
  
  Preview and build the React Router application
&lt;/h3&gt;

&lt;p&gt;Run the following command to build the React Router application&lt;br&gt;
and store the static files into the Rails &lt;code&gt;public&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails react_router:build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command is also aliased as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails react_router:preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start your Rails application if it is not already running and point your browser to &lt;a href="http://localhost:3000/react/" rel="noopener noreferrer"&gt;http://localhost:3000/react/&lt;/a&gt; to see the welcome page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read the code
&lt;/h3&gt;

&lt;p&gt;We have added numerous comments to the code generated by this gem. Please read it to understand how the integration works.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Rails View Helpers for Components</title>
      <dc:creator>Naofumi Kagami</dc:creator>
      <pubDate>Mon, 23 Dec 2024 01:00:32 +0000</pubDate>
      <link>https://dev.to/naofumik/rails-view-helpers-for-components-481c</link>
      <guid>https://dev.to/naofumik/rails-view-helpers-for-components-481c</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is intended to gather feedback on a proposal for a new feature for Rails, while I put together a pull request. I welcome any comments, either here or on X or BlueSky&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL:DR
&lt;/h2&gt;

&lt;p&gt;I want to propose an extension to Ruby on Rails TagHelpers which will enable us to create HTML structures that were cumbersome before.&lt;/p&gt;

&lt;p&gt;In short, the view helper code to write this,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/path/to/image.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Alternate Text"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;Figure Caption&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could look like this (with my extension proposal),&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of this (how we would currently write this with view helpers),&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;figure_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
  &lt;span class="n"&gt;figure_content&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;figure_content&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proposed approach allows us to write more complex HTML with tag helpers alone (without ERb), using code that closely mirrors the HTML structure. This will simplify the creation of UI components.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The HTML and view helper code snippets above come from &lt;a href="https://garrettdimon.com/journal/posts/erb-partials-helpers-and-rails" rel="noopener noreferrer"&gt;Garrett Dimon's blog post&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Credit to Garrett Dimon and Adam McCrea
&lt;/h2&gt;

&lt;p&gt;Garrett Dimon wrote an excellent post &lt;a href="https://garrettdimon.com/journal/posts/erb-partials-helpers-and-rails" rel="noopener noreferrer"&gt;"Structure Your ERb and Partials for more Maintainable Front-end Code in Rails"&lt;/a&gt; where he discusses alternative approaches to using view helpers and partials, together with points to consider when choosing between the two.&lt;/p&gt;

&lt;p&gt;Adam McCrea wrote a post in response, &lt;a href="https://judoscale.com/blog/phlex-not-erb" rel="noopener noreferrer"&gt;"Say No To Partials And Helpers For A Maintainable Rails Front-End"&lt;/a&gt;, proposing "throwing out ERB partials and helpers altogether, and using Phlex instead".&lt;/p&gt;

&lt;p&gt;Although I fully agree that Phlex is fantastic and a breath of fresh air, I hope my proposal addresses at least one of Adam's major complaints and will allow us to positively re-evaluate traditional Rails ERb and view helpers. &lt;/p&gt;

&lt;p&gt;In short, I don't want to throw the baby out with the bathwater.😀&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's first look at ViewComponents and Phlex
&lt;/h2&gt;

&lt;p&gt;Before diving into the details of what I am proposing, let's first look at how you would write the HTML above using ViewComponents and Phlex.&lt;/p&gt;

&lt;p&gt;I hope this illustrates my point, which can be summarised as "Why do we need to write a new class for just 4 lines of HTML?".&lt;/p&gt;

&lt;p&gt;I think there is a better way.&lt;/p&gt;

&lt;h3&gt;
  
  
  ViewComponents
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;figure_component.rb&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FigureComponent&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ViewComponent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
    &lt;span class="vi"&gt;@alt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;
    &lt;span class="vi"&gt;@figcaption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;figure_component.html.erb&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@src&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@alt&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@figcaption&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Phlex
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Components::Figure&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Components&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;
    &lt;span class="vi"&gt;@alt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;
    &lt;span class="vi"&gt;@figcaption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_template&lt;/span&gt;
    &lt;span class="n"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;src: &lt;/span&gt;&lt;span class="vi"&gt;@src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;alt: &lt;/span&gt;&lt;span class="vi"&gt;@alt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;figcaption&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@figcaption&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@figcaption&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  This proposal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The need for a simpler approach
&lt;/h2&gt;

&lt;p&gt;Both ViewComponent and Phlex are class based and are great for components that need to encapsulate logic. &lt;/p&gt;

&lt;p&gt;However, with the rise of Tailwind CSS, we are encouraged to write a lot of smaller components whose only purpose is to DRY our code and save us from writing a ton of CSS classes – they have very little internal logic. In these cases, ViewComponents and Phlex can be an overkill.&lt;/p&gt;

&lt;p&gt;In many cases, traditional view helpers included in Rails are sufficient for this. For the following HTML for example, you can write a very easy-to-understand helper that mirrors the HTML structure like this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/path/to/image.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Alternate Text"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;view helper&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, this breaks down when the &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; element has more than one child. &lt;/p&gt;

&lt;p&gt;For the following HTML where &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; has two children, you could either write a hard-to-read view helper, or more often, you would use ERb. &lt;/p&gt;

&lt;p&gt;Although I fully appreciate that ERb is better than view helpers for rendering highly complex HTML, I find it hard to accept the idea that simply increasing the number of children from one to two makes the HTML substantially more complex. I am uncomfortable with the thought that this alone should compel us to use ERb instead of view helpers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTML&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/path/to/image.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Alternate Text"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;Figure Caption&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;view helper (hard-to-read)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;figure_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
  &lt;span class="n"&gt;figure_content&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;figure_content&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ERb (you would often use this instead of the above view helper)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given this situation, I propose an idea to make it easier to write HTML elements with more than one child in view helpers, by adding a feature to tag helpers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why don't tag helpers work when there is more than one child?
&lt;/h2&gt;

&lt;p&gt;If we write the following code in a view helper, we lose the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag and only get the &lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is because the return value of the &lt;code&gt;do&lt;/code&gt; block is used as the content for the &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; tag. Since &lt;code&gt;tag.figcaption&lt;/code&gt; is the last expression in the block, this value becomes the content. The &lt;code&gt;tag.img&lt;/code&gt; is lost because its value is not captured anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;view helper&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;HTML&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;Figure Caption&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Proposed solution
&lt;/h2&gt;

&lt;p&gt;I propose the following syntax. We extend tag helpers to optionally provide a variable (in this case &lt;code&gt;b&lt;/code&gt; for "buffer") to the block. We push the &lt;code&gt;tag.img&lt;/code&gt; and &lt;code&gt;tag.figcaption&lt;/code&gt; to this buffer (&lt;code&gt;b&lt;/code&gt;), which is implemented as an Array. After the tag helper yields the block, it performs a &lt;code&gt;safe_join&lt;/code&gt; on the buffer resulting in HTML that includes both child elements, and will be used as content for the &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="ss"&gt;figcaption: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;figcaption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code that this enables closely aligns with the structure of the HTML and is easy to compare. Even people with minimal understanding of Ruby should be able to understand what is happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;Through this proposed feature, developers who are hesitant to use ViewComponents or Phlex can better benefit from components while sticking to traditional ERb and view helpers.&lt;/p&gt;

&lt;p&gt;It also promotes writing components using functions (view helpers), in a way that is easy for non-Ruby developers (i.e. designers) to understand. Note that modern React components are also functions, not classes.&lt;/p&gt;

&lt;p&gt;This feature enables us to implement slightly more complex components in view helpers without resorting to ERb templates. Compared to ERb, the benefits are the ability to fit multiple components in a single file, an explicit interface, a simpler API for calling (you don't need to write &lt;code&gt;render "[path to partial]", [hash of locals]&lt;/code&gt; which can become verbose), and performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about unit testing?
&lt;/h2&gt;

&lt;p&gt;One of the commonly mentioned benefits of using ViewComponents or Phlex instead of ERb and view helpers, is the ability to unit test components.&lt;/p&gt;

&lt;p&gt;I do not know where this idea originated from, but I do know that the Rails Guides has sections for &lt;a href="https://guides.rubyonrails.org/testing.html#testing-view-partials" rel="noopener noreferrer"&gt;unit testing ERb partials&lt;/a&gt; and for &lt;a href="https://guides.rubyonrails.org/testing.html#testing-view-helpers" rel="noopener noreferrer"&gt;unit testing view helpers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have been unit testing ERb partials and HTML-generating view helpers for several years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Although I have been using a variation of this technique for many years, I am proposing to add this to Rails to make it more convenient and to increase awareness of the &lt;code&gt;safe_join&lt;/code&gt; technique.&lt;/p&gt;

&lt;p&gt;I am currently preparing an implementation and a pull request. In the meantime, if you have any comments or opinions, I would love to hear them!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
