Radoslav Stankov

Component Driven UI with ViewComponent gem

A talk from RubyConfTH 2023, held in Bangkok, Thailand on October 6-7, 2023.
Find out more and register for updates for our next conference at https://rubyconfth.com/

RubyConf TH 2023

00:00:09.080 Thank you! Thank you! As mentioned, I'm Radoslav Stankov.
00:00:16.320 I come from Bulgaria, which is located over here. I'm going to show a lot of cats in my slides. I usually notice glimpses of things happening when I present, so I simply take pictures. I already have them prepared here.
00:00:22.480 You don’t need to worry about them, as I will switch to them.
00:00:27.840 As mentioned, I used to be the head of engineering at ProduHun. This was the architecture for ProduHun because it was a social app, which required a very interesting and fast front end.
00:00:38.640 We used Ruby on Rails, the GraphQL Ruby gem, and Apollo GraphQL. This architecture worked very well for a product like ProduHun.
00:00:49.760 While at ProduHun, I started a project called Angry Building, which is a SaaS tool for facility managers. The goal of the company is to rename it to Happy Building and make everyone happy, but we have to start somewhere. So we started with the Angry side, and it began as a side project that I worked on during weekends.
00:01:06.200 Initially, I decided to use the simplest architecture I could manage, which involved using Rails and avoiding JavaScript as much as possible, because although I like JavaScript, it can complicate things.
00:01:18.920 I like CSS, but I didn't want to deal with naming class names. Naming is hard, and if I could avoid it, I tried to.
00:01:32.920 So I decided to use Tailwind because this was a business application with extensive end-to-end tests, and the biggest focus was on the domain logic. I love Ruby and Rails, but there are parts of every Rails application that can be dark corners: the helpers folder, and the other one, which is even darker because it's intertwined with the view logic, called partials.
00:02:03.360 In many of our projects, when I try to visualize how something will look, I end up pulling together strings of partials. How many of you have been in a situation where you click on one partial, then another, and then another, and for some reason, the first partial is included in the second one? It creates double logic, and I was not very happy about that.
00:02:35.040 The UI may not have been simple, but I wanted my life to be simpler. In 2020, GitHub released a gem called ViewComponents, which has a very simple concept: you just write normal classes for components and create view components that help you encapsulate logic.
00:03:04.640 The nice thing is that I didn't have to rely on extra tools like presenters or decorators, which made me question the differences between decorators and presenters. So I started using this library, and today my talk will cover some things I learned. Before we dive into those lessons, let's have a quick overview of what the ViewComponent library is and how it can be applied in the real world.
00:03:36.400 If you think about a very simple UI, it could be a field set for a form. This component represents the field set, which can have a title and content that can be configured and used in the view layer. In this example, we simply render a field set component, provide a title, and add some content. It utilizes a form field set that inherits from a base view component class.
00:04:36.720 You can create an HTML view that ties to this component, where you provide a title, and you have this magical variable called 'content', which captures the block value passed to the component. This is one of the most powerful features of the library, and I'm going to share many examples of it.
00:05:04.800 Having this component is actually very straightforward; it encapsulates the logic well. Because this is the Ruby community and we care about developer experience, the ViewComponent gem comes with a preview system that allows you to define previews similarly to how Rails action mailers do.
00:05:36.720 You just generate a simple preview class. There’s also an additional gem called Lookbook that lets you preview and test your components easily. When I was deeply involved in React work, setting up comparable tools like Playbook took me a week to make it work and several hours each month just to maintain, but with this, I can set it up, and it works out of the box.
00:05:52.920 So if you know how to use ViewComponents, you can start using it right away, and that's all great. My talk could be over in less than five minutes, but let's explore a bit deeper. When I began using ViewComponents, just with the basics, it took me quite some time to figure out how to use them effectively.
00:06:20.120 Over time, I noticed many questions arising around what should be a helper, whether I should stop using helpers completely, and what should be a partial versus a component. It’s a tricky balance, and, like many developer-related questions, the best answer usually is: it depends.
00:06:54.560 So I developed a mental checklist for deciding what belongs in view components, partials, and helper functions. I typically use a view component when I see a partial used in at least two controllers, because if a partial is shared between controllers, that indicates some domain logic that spans across boundaries, where it definitely belongs in a component.
00:07:45.000 If I have a view helper generating HTML, it can become tricky. I noticed that deep, complicated logic based on a single domain object entity is best placed in a component, where I can use private methods to split the logic and make it more readable. Although I try to write as little JavaScript as possible, the JavaScript I do write is often extracted into a component for easier maintenance.
00:08:01.680 I generally do not use view components for a partial named underscore form. I think most of you have created underbar forms, which are typically used in edit actions. If the partial is only used in a single controller, that's usually fine to keep as-is.
00:08:38.560 Another example is a view helper that serves as a pure function, like a function called 'format money' which simply formats a money object. There’s a bit of controversy here, but if a page has a lot of HTML needed for the UI, I may decide to keep it messy until I find the right abstraction to refactor it.
00:09:07.040 Trying to split it into twenty partials when the initial one would read better isn’t always effective. It can be challenging to spot duplications when you need to open numerous files to verify; for me, it's easier to view everything on one big monitor and scroll.
00:09:52.880 I mentally categorize my helper functions that process data, which leads to UI components—these represent UI elements devoid of any domain logic. They are combined with domain components related to your business logic.
00:10:51.040 For instance, a 'format money' method feeds into a money component so that if it's negative, it shows in red, and if positive, it's green. I also have a component that displays product prices. If you provide it a product, it checks price conditions like discounts.
00:11:13.600 This framework allowed me to separate UI logic from domain-specific logic. I’ve used similar logic in the React world, where I distinguish between UI components and domain components. Let’s take a look at some code examples.
00:11:45.559 Initially, I found it not very readable to use 'render' for component initiation, so I created my helper function named 'component'. It uses this convention of configuration, making it nicer to read when I say ‘component’ multiple times in succession.
00:12:36.760 Now, let’s see an example from my application. Here we have a common screen with various areas. If we visualize the layout, you’ll see components for navigation, page header, filter form, stats, and table.
00:13:11.720 Let’s break these components down, starting with the stats component, which is very simple. The stats component facilitates the display of various statistical numbers, providing flexibility for count and title inputs.
00:13:54.480 It also utilizes a feature of ViewComponents called slots, allowing other view components and HTML to be dynamically inserted, contributing to its reusability. In this case, the numbers utilize a component specifically designed for rendering those statistics.
00:14:32.680 Another pattern I employ in ViewComponents is to alias methods for better clarity. For example, instead of ‘render many with numbers,’ I’ve created a simpler ‘number’ alias. This approach improves the API readability. Furthermore, I have a root component to gather useful methods while maintaining simplicity and flexibility in configurations.
00:15:19.560 In addition, I have a method that safely fetches options from hashes without throwing errors for user experience purposes. This method ensures that users do not experience application crashes over minor configuration errors.
00:16:22.760 Now, let’s move on to the Filter Form component, which employs a pattern similar to Rails' form builder. This component simplifies the creation of filter forms, capturing relevant parameters you need to match data.
00:17:21.600 The component is structured to include actions and maintain serialization of data between the UI and other aspects of the application. I often include pre-render callbacks in these components to maximize efficiency.
00:18:10.640 This pattern helps streamline the presentation of various inputs and options, as I leverage existing helper definitions throughout the component lifecycle. These definitions help maintain quick access to HTML standards that encapsulate the application’s intents.
00:18:54.160 Currently, when implementing the Day Range component, I ensure it's versatile enough to adapt to future designs. If a designer requests a fancier UI, I can easily evolve this component while keeping the underlying core intact.
00:19:33.520 Now, let's discuss the Page Header component. This component integrates breadcrumbs and an actions bar, enabling users to navigate efficiently through the app's structure.
00:20:00.120 The Page Header design uses simple slots, where I can pass multiple breadcrumbs without complication, ensuring readability while maintaining structure. Titles also leverage the internationalization features, utilizing display functions to provide context-aware naming.
00:20:45.160 An interesting implementation detail is passing additional data back up the tree to populate the main HTML header title dynamically, which I have applied strategically only where necessary.
00:21:32.320 The final component I’ll cover is the Table component, necessary for displaying business data systematically. This component streamlines configurations for displaying row data, invoking internal domain logic to present the relevant user interface.
00:22:21.360 Throughout this, I included a variety of formatting helpers with specific logic for handling presentation while maintaining clear readability across the component structure.
00:23:05.600 In managing the Table component, I ensured encapsulation of additional complexity through header and data configuration while keeping the component configuration nearby for clarity.
00:23:58.240 Each column can incorporate varying formatting methods, ensuring a responsive design that adapts to business requirements and suitably presents the data visually.
00:24:39.760 In conclusion, I have about 20 or 30 more components that are simpler than the ones I demonstrated. However, the examples I shared are core components used throughout many of the applications I’ve designed. Thank you for your attention!
00:25:29.360 The slides will be available soon, and if anyone has questions, I’ll be wandering around.