Hotwire Vs React: Which Should You Pick In 2026?

Hotwire vs React in 2026: discover which approach fits your project best, with real code examples, and honest tradeoffs.

Jean Emmanuel Cadet
By Jean Emmanuel Cadet Full-Stack Ruby on Rails Developer
Hotwire vs React: Which Should You Pick in 2026?

• 10 minutes read

Share with friends
You sat down, opened your editor, and stared at that blinking cursor for a little longer than you expected.

You had a Rails app. You needed interactivity. And someone in a forum said, "Just use React," while someone else said, "Hotwire is all you need." Now you are somewhere in between, unsure which road to take, afraid of picking the wrong one.

I have been exactly there. And I want to tell you something before we go any further: both tools are good. Both have a place. The goal of this article is not to declare a winner. The goal is to help you understand each one so well that when you finish reading, the choice becomes obvious for your specific situation.

Let us walk through this together.


First, Understand the Problem They Both Solve

Every web app eventually needs to do something without refreshing the whole page. A button that submits a form. A list that updates live. A counter that ticks. A notification that pops up.

For years, Rails developers had to choose between writing heavy JavaScript themselves or reaching for a framework like React or Vue. It felt like there was no middle ground.

Then Hotwire arrived and said, "What if the server could just send HTML and update the page for you?"

And React had already been saying for years: "What if the browser could handle everything, and the server just sent data?"

These are two genuinely different philosophies. And in 2026, both are mature, both are in production, and both deserve a fair look.


What Is Hotwire?

Hotwire stands for HTML Over the Wire. It is a set of tools, built by the Rails team and Basecamp, designed to make modern web pages feel fast and interactive without writing a lot of JavaScript.

Hotwire has three parts:

Turbo handles page navigation and form submissions. Instead of full page reloads, it replaces parts of your HTML using something called Turbo Frames and Turbo Streams.

Stimulus is a lightweight JavaScript framework that connects your HTML to small, focused controllers for behaviors like toggling menus, validating fields, or animating elements.

Hotwire Native is a web-first framework for building native mobile apps.

The philosophy is simple: keep the logic on the server, send HTML to the browser, and let the browser update itself smartly. If you are already building Rails apps, this feels natural. Almost magical.


What Is React?

React is a JavaScript library built by Meta. It lets you build user interfaces using components, where each component manages its own state and renders itself whenever that state changes.

Traditionally, React applications rendered much of their UI in the browser. Today, frameworks like Next.js can render React on the server, in the browser, or a combination of both.

React powers some of the most complex UIs in the world, including Facebook, Instagram, Notion, and countless others. It has a massive ecosystem, strong community support, and years of battle-tested patterns behind it.

The philosophy is: move the complexity to the client, give the browser more power, and let the server focus on being an API.


Real Code: The Same Feature, Two Ways

Let us build one real feature using both approaches so you can feel the difference, not just read about it.

The feature: A simple counter that increments when you click a button.


Hotwire Version
In your Rails controller:
# app/controllers/counters_controller.rb
class CountersController < ApplicationController
  def show
    @count = session[:count] || 0
  end

  def increment
    session[:count] = (session[:count] || 0) + 1
    @count = session[:count]
    respond_to do |format|
      format.turbo_stream do
        render turbo_stream: turbo_stream.update("counter", partial: "counters/count", locals: { count: @count })
      end
      format.html { redirect_to counter_path }
    end
  end
end

In your view:
<%# app/views/counters/show.html.erb %>
<div id="counter">
  <%= render "count", count: @count %>
</div>

<%= button_to "Increment", increment_counter_path, method: :post %>

In your partial:
<%# app/views/counters/_count.html.erb %>
<p>Count: <%= count %></p>

In your route:
# config/routes.rb

resource :counter, only: [:show] do
  post :increment
end
That is it. No JavaScript file. No state management library. The server handles the count, sends back a Turbo Stream response, and the browser updates only the counter div. The page does not reload.


React Version
// Counter.jsx
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

This is also clean and simple. The state lives entirely in the browser. No server call needed. Click the button, and React re-renders only the part of the UI that changed.


What You Just Saw

Both examples are clean. Both work. But they live in different worlds.

The Hotwire version keeps the count on the server. If the user refreshes the page, the count is still there (because it is stored in the session). The server is the source of truth.

The React version keeps the count in the browser. If the user refreshes, the count resets. For a persistent state, you would need an API, a database call, and some fetch logic.

Neither is wrong. They just make different assumptions about where your data should live.


A More Real-World Example: Live Search

Let us look at something more practical: a search input that filters results as you type.

Stimulus Search Example
// app/javascript/controllers/search_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["input", "results"]

  search() {
    clearTimeout(this.timeout)
    this.timeout = setTimeout(() => {
      const query = this.inputTarget.value
      fetch(`/articles/search?q=${query}`, {
        headers: { "Accept": "text/html" }
      })
        .then(response => response.text())
        .then(html => {
          this.resultsTarget.innerHTML = html
        })
    }, 300)
  }
}

In your controller:
def search
  @articles = Article.search(params[:q])
end

In your view:
<%# In your view %>
<div data-controller="search">
  <input data-search-target="input" data-action="input->search#search" type="text" placeholder="Search articles..." />
  <div data-search-target="results">
    <%= render @articles %>
  </div>
</div>


React Version
// SearchBar.jsx
import { useState, useEffect } from "react";

export default function SearchBar() {
  const [query, setQuery] = useState("");
  const [articles, setArticles] = useState([]);

  useEffect(() => {
    const timer = setTimeout(() => {
      if (query.length > 0) {
        fetch(`/api/articles?q=${query}`)
          .then(res => res.json())
          .then(data => setArticles(data));
      }
    }, 300);

    return () => clearTimeout(timer);
  }, [query]);

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search articles..."
      />
      <ul>
        {articles.map((article) => (
          <li key={article.id}>{article.title}</li>
        ))}
      </ul>
    </div>
  );
}

Both versions achieve live search. The Hotwire version returns rendered HTML from the server. The React version returns JSON and renders it in the browser. The Hotwire version means your views stay in Rails. The React version means you need a JSON API endpoint.


Where Hotwire Shines

Hotwire is the right choice when:

You are building a Rails app and want to stay in Rails. No context switching between Ruby and JavaScript. Your views, your logic, and your validations all live in one place. This is a productivity superpower.

Your interactions are form-based. Submitting forms, updating lists, showing flash messages, toggling sections: Hotwire handles all of these beautifully with almost no code.

Your team is small or Rails-focused. Adding React to a Rails app often means maintaining two separate codebases. Hotwire lets you stay focused.

SEO matters. Because Hotwire sends real HTML from the server, search engines can crawl your content without any extra configuration. Server-side rendering often improves SEO and initial page load performance for React applications, though modern search engines can index many client-rendered React applications as well.

You want a faster time to production.
Less setup, fewer dependencies, smaller bundle sizes. Hotwire gets you to a working, interactive feature faster.

If you are just getting started with Rails and want to understand the foundation before adding frontend complexity, I recommend reading How to Build a Blog with Ruby on Rails 8 first. It will give you the confidence to build something real before you worry about frontend choices.


Where React Shines

React is the right choice when:

You are building a highly interactive, stateful UI. Think dashboards with drag-and-drop, real-time collaboration tools, complex data visualizations, or multi-step wizards with lots of local state.

Your frontend team has React expertise. A team of React developers will build faster and better in React than they will trying to learn Hotwire.

You are building a mobile app alongside your web app. React Native shares knowledge and patterns with React. If you plan to build both, React on the web makes sense.

You need offline support. React (with service workers) can power apps that work without an internet connection. Hotwire is server-dependent by design.

Your product is essentially a single-page application. If users spend hours inside your app, navigating many views, managing lots of state, and expect app-like smoothness: React is built for this.

Can You Use Both Together?

Absolutely.

Many Rails applications use Hotwire for most interactions and React only for specific components that require rich client-side behavior.

This hybrid approach often provides the productivity benefits of Rails while still allowing complex interfaces where they are needed.


The Honest Tradeoffs

Hotwire can get complicated at scale. When you have deeply nested Turbo Frames, multiple concurrent Turbo Streams, and complex UI state to manage, things get harder to debug. The server-centric model that feels simple at first can become a puzzle when interactions grow.

React has a steep learning curve and heavy tooling. If you are new to frontend development, React requires you to understand components, state, effects, hooks, bundlers, and often TypeScript. The ecosystem is powerful but overwhelming. Build times slow down. Bundle sizes grow. And you still need a backend.

Hotwire is not JavaScript-free. Stimulus is JavaScript. You will still write JS. The difference is that Stimulus encourages small, focused controllers rather than large application states.

React is not always fast. A poorly optimized React app with large bundles and unnecessary re-renders can feel slower than a well-built Hotwire app. Speed comes from good engineering, not from the framework.


My Recommendation for Rails Developers

If you're primarily building Rails applications, I would usually start with Hotwire and introduce React only when the product genuinely benefits from a client-heavy interface. It is fast to learn, fast to build with, and handles 80 to 90 percent of what most web apps need.

React is not going away. It is not wrong. But for Rails developers, it often solves a problem you do not have yet by creating new ones you were not expecting.

The best developers I know are not loyal to tools. They are loyal to shipping software that works. Use Hotwire until you feel the pain that it cannot solve. Then reach for React exactly where you need it.

If you ever feel stuck or overwhelmed on this journey, know that the path from confused beginner to confident developer is real and walkable. Read Master Ruby and Rails Challenges: From Overwhelmed to Pro. It is a reminder that every expert you admire was once exactly where you are.


Here Is My Advice

Do not just read this article and close the tab. Build something.

If you want to start with Hotwire, add a Turbo Frame to a form in an existing Rails app. Watch it submit without a page reload. Feel that.

If you want to explore React, build the counter example above. Extend it. Break it. Fix it. Learn by doing.

The developers who grow the fastest are not the ones who read the most. They are the ones who build, fail, ask good questions, and keep going.

You are already asking good questions. That is how I know you are on the right track.

Keep building.

💌 Don’t miss out! Join my newsletter for web development tips, tutorials, and insights delivered straight to your inbox.

Thanks for reading & Happy coding! 🚀

Follow me on:

From My Dev Desk — Code, Curiosity & Coffee

A friendly newsletter where I share: Tips, lessons, and small wins from my dev journey, straight to your inbox.

    No spam. Unsubscribe anytime.