Skip to content

A React Developer in a Ruby World

Photo of Michał Janiec
Michał Janiec
Frontend Developer

For the last few years, I worked mostly on projects where React was the main frontend technology. When the opportunity came up to work in a completely different environment, I had a lot of doubts.

I had become used to the comfort that frameworks and libraries provide. They help us build complex applications faster, hide many implementation details, and make day-to-day work smoother. Over time, I realized that working in React can also make it easy to stop thinking about some of the lower-level patterns and rules that matter when writing plain JavaScript.

Almost a year ago, I joined Visuality to work on a large project with a clear mission: help redesign an existing application. It is a complex internal system with many advanced layouts and a lot of forms, originally built with Bootstrap and .html.haml. Our goal was to introduce a new design using .html.erb for templates, Tailwind CSS as a more modern alternative to Bootstrap, and Stimulus.js as a lightweight way to add the necessary JavaScript behavior to the UI.

In the React world, many things feel automatic: rendering, re-rendering, event handling, and UI updates. On top of that, libraries like Material UI or Chakra UI give you ready-made components that work out of the box. In a Rails setup built around ERB templates, Tailwind, and Stimulus, you quickly find yourself back at the fundamentals of frontend development. At first, that can feel like a step backward. For me, it turned out to be the opposite. In this post, I want to share what I learned from that transition and why I think React developers can bring a lot of value to a Ruby on Rails environment.

Thinking in Components, Even When There Are No Components

The biggest surprise for me was how natural ERB templating felt. Even without React components, I could build the UI in a very similar way: by creating small partials or view components and composing them into more complex layouts. With the right level of parameterization, those building blocks can be reused across many different screens.

This is where my React experience proved especially useful. Thinking about interfaces as collections of smaller, reusable pieces helps in a Rails project too. It improves structure, makes the UI easier to reason about, and reduces duplication.

That said, there is an important limit here. Making partials too generic is not always a good idea. It is easy to end up with a component that has 12 parameters and 20 conditionals just to support every variation. At that point, the component often becomes more confusing than helpful. Sometimes the better choice is to keep things simple and create a new, cleaner version instead of stretching an existing one to fit yet another Figma design.

Where Is the Line Between Frontend and Backend/Hotwire Responsibilities?

In more typical projects, the boundary is clear: the React frontend usually owns most UI logic, while the backend provides an API. In a Rails application especially one built with Hotwire/Turbo that boundary looks different. The frontend delivers markup that includes different UI states and basic behavior, while the backend ties everything together by handling validation, responses, and UI updates.

In this kind of setup, React experience can still help because it encourages thinking in terms of contracts. What should be prepared on the UI side before backend integration? What should remain client-side behavior handled by Stimulus? And what belongs to domain logic?

A real example is form handling.

  • States such as disabled, readonly, and error should be considered during frontend work.
  • Business validation, on the other hand, should remain a backend responsibility.
  • Input masks and value formatting are usually frontend tasks too, but they require close collaboration with the backend developer to ensure the final value is parsed and passed in a format the backend expects.

This way of thinking is important to learn and apply in team workflows, because it saves time and helps produce frontend code that is easier to integrate with Ruby later.

Creating UI as a Consistent System

In React-based projects, many UI rules are shaped by the design system or component library chosen at the start. That gives the team a baseline level of consistency almost for free.

During a redesign especially when moving from Bootstrap to Tailwind those decisions have to be made consciously. You can no longer rely on the library to enforce patterns everywhere. The team has to define them itself.

This is where frontend experience becomes a real advantage in a fullstack-oriented team. Keeping UI states like hover, focus, and active consistent, and applying the same discipline to elements such as popups, tables, and modals, helps turn a redesign into a coherent product rather than just a collection of updated screens.

Designing for Edge Cases

One thing I noticed very quickly in this project was that mobile responsiveness was not the main challenge. A much more important case was browser zoom on large screens, because many users rely on it in their everyday work. That changes the whole perspective on responsiveness. The goal is no longer just to make the layout work across breakpoints, but to make components resilient in real usage conditions.

This is where edge cases start to matter a lot.

As a React developer, I am used to thinking about them, because in SPA projects they always come back during QA. In a Rails application, they often show up even earlier, because you are working much closer to raw HTML and browser behavior. Suddenly, long text, table layouts, dropdown positioning, and overflow handling are not minor details anymore — they become part of the core implementation work.

Many of these are classic frontend problems that modern UI libraries quietly solve for you. Once those abstractions are gone, you have to solve them yourself. And that is exactly where experience from modern frontend work becomes valuable.

Do You Really Need TypeScript Here?

When I was starting out in frontend development, TypeScript was one of the technologies I wanted to learn most. In modern frontend projects, one of its biggest strengths is that it makes development feel more predictable. It provides stronger contracts, better editor support, and more confidence as the codebase grows.

That is why I was a bit confused when I realized the stack I was joining did not include TypeScript. I immediately wondered whether it would still be possible to build code that felt safe, maintainable, and easy to extend.

Once I started working with Stimulus, I realized that most of my JavaScript was focused on relatively small, local behaviors attached to server-rendered HTML. I was dealing more with DOM structure, data attributes, and focused UI interactions than with large client-side state models. In that kind of setup, the lack of TypeScript felt much less painful than I had expected. Because of that, I did not run into the same kind of type complexity I was used to in larger React applications. In practice, I could focus more on shipping features and less on aligning or maintaining type definitions.

Working in this environment also made me rethink some of my assumptions even further. I still think TypeScript is very valuable, especially in large, frontend-heavy applications. But in projects with a lighter JavaScript layer, it can sometimes become an extra layer that adds friction instead of reducing it. This experience did not make me anti-TypeScript. It simply helped me see more clearly that its value depends a lot on context.


Going Back to Fundamentals Shows How Far Frontend Has Come

Working with a stack like Rails, Tailwind, and Stimulus pushed me back toward the roots of frontend development. It reminded me that building reliable, reusable UI often requires far more thought than modern tools make visible.

The best example for me was a select component with a dropdown that needed to work everywhere, no matter where it was rendered. In a typical React project, I would probably import a ready-made component from MUI, Chakra UI, or a similar library. I might tweak a few styles, but most of the hard work would already be done.

Here, that abstraction was gone. I had to think through the implementation details and every context in which the component could appear. One of the most common problems was rendering the dropdown inside containers with overflow: hidden. I tried several approaches, including some based on modern CSS techniques, but none felt reliable across all cases.

In the end, it took a lot of iteration and more than 300 lines in a Stimulus controller to build a solution I could trust. The lesson was simple: when you work closer to the fundamentals, you become more careful and more aware of the real complexity of UI work. Modern libraries often protect us from that complexity, but they can also hide it.

Summary

If I had to distill this whole experience into one takeaway, it would be this: do not be afraid to move into a new stack, even if it first feels like a step backward. For me, that meant going back to something much closer to vanilla JavaScript than what I had been used to in recent years.

I still value React highly. It makes it easier to build large applications, organize UI into reusable pieces, and avoid dealing directly with some of JavaScript's lower-level complexity day to day. But that is exactly why working in Rails with ERB, Tailwind, and Stimulus turned out to be so valuable for me. It forced me to reconnect with the fundamentals.

That return to the basics was not a downgrade. It made me more aware of how frontend really works beneath the abstractions. It reminded me that details such as event handling, DOM structure, overflow behavior, and UI states are not minor implementation details, they are often the difference between something that merely looks good and something that actually works well.

That is probably the biggest thing I took from this experience: modern tools make us faster, but they can also hide complexity that is still there. Stepping outside your default stack once in a while can be one of the best ways to sharpen your engineering instincts.

In the next posts, I want to share more concrete examples from Rails projects, both the things that make development easier than expected and the traps that can quickly become painful if you do not account for them early.

Did you like it?

Sign up To Visuality newsletter