The Power of Words in Growing Up

Capita partnered with AIR Serenbe, the non-profit artist-in-residence program at Serenbe, to produce a short film featuring eight of America’s leading spoken word artists reflecting on the experience…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




Comparing Web Philosophies for a UI Problem

The front-end ecosystem has exploded in recent years. Although the “winner” at the time of this writing appears to be React, it’s not the only possible solution, and is often used in cases where it shouldn’t be.

Here’s a case study of an unusual problem and a number of different approaches I attempted to use to solve it.

With React you build actual pages as “components”, Because of this, I’ve seen many cases where each page can have several page-specific subcomponents. In the extreme end, a mostly static page can be comprised of dozens of components, many of which are designed only for that specific page.

With Stimulus, since you’re generating HTML on the server side, there’s usually no need to make any page-specific controllers. I made a “toggle controller” (which toggles an element’s visibility based on the value of a drop-down), an “add row controller” (which adds a row to a table when a button is clicked), etc., and let the rendered HTML take care of the specific content.

With this approach, the total lines of code dropped by about 9,000 — that includes all the HTML and server code I added and the tests that didn’t exist that were created as part of the rewrite. The end result had less than 400 lines of JavaScript.

Stimulus is a lot more flexible than React, which is a blessing and a curse. You can add your controllers literally anywhere as long as the correct attributes are set on your elements. But because of that, you don’t get that nice, boxed-in feeling of owning your entire component and defining how it’s used, or immediately understanding how to “call” it. There’s a limit you can reach fairly easily where you need more boundaries.

I mostly avoided that limit in my rewrite, but there was one task which seemed to bump up against it: A paginated, filtered table with static, client-side data.

The reason this had to be fully client-side is that rather than fetching data from a database, we were listing objects (services, functions, etc.) from AWS API calls. These API calls could only be tokenized, not paginated (you can’t say “give me page 4 of this API call”). So we couldn’t paginate this reasonably anywhere but the browser.

In addition, I wanted a generic solution, because this is a pattern that repeated multiple times throughout the application, and the tables and filters look slightly different each time. I didn’t want to create separate controllers for each of my pages when the functionality it’s trying to impart is practically identical.

Without pasting the full solution, some sample HTML that could use my Stimulus controller might look like this:

This hooks up the individual input fields with the controller (which it uses both for event handling and reading the values), as well as the table itself. The rows are given HTML data attributes to determine what the filterable values are for each row.

The nicest thing about this is that it does not care what the rows themselves look like. The filters can also live anywhere on the page — they could be three layers deep in divs. We literally “sprinkle” the ability to filter and paginate our table onto any HTML we want.

Another advantage is that we get immediate reference to each element we need to interact with — we don’t need to query the DOM tree to determine where each of these elements live.

There are a couple of downsides, though. One is that all rows need to be rendered initially, and they are filtered as you navigate and type search text. This can introduce performance problems at a high enough scale. (Arguably if you get this far, you anyway need to move past this and introduce some kind of server-side caching and use real pagination.)

A bigger thing that rubs me the wrong way is the pagination piece. My Stimulus controller actually generates the navigation menu, which is against its normal philosophy. Unfortunately there’s simply no way around this — the controller doesn’t know in advance how many pages to display, and the pages in view will change as the user paginates and filters, so we have to recreate it from the ground up each time.

This got me to thinking how React would handle this kind of case — where we want to have rows that could look arbitrary, and filters that could live in arbitrary spots in the page. More importantly, could React handle this outside a “React app”? Could we drop a React component into an HTML page and have it work? This is definitely not the “standard” React ecosystem, where you are expected to build everything from the ground up.

The idea in my head here was to have some kind of generic “Filters” component which could be swapped out to render different kinds of filters, and a generic “TableRow” component that could render the row. Thinking in object-orientation, it would be great to define an interface or “parent component” that is expected to behave a certain way, and then allow us to pass in “child components” that implement that behavior.

Making a “generic” or high-level component is possible in React, but most cases I’ve seen just take their props.children and re-render them as-is. This halfway point where the children are “arbitrary” but actually need to conform to a particular kind of behavior doesn’t fit in with normal use.

One way this might look is something like this:

Rendering the rows can be done in a somewhat straightforward manner by creating a function that returns some JSX based on the data it’s passed in, and calling that function when it’s time to populate the table. Filters, on the other hand, are a lot more complicated, particularly if they can be in arbitrary places. We have to make different child components for each layout type, and possibly for each page that we want this on.

We would need to define some kind of filter function for each filter, which has to be made available to the parent component by one of the multitudinous state patterns in React (context, Redux, useState, render props, etc.). Finally, we need to hook up all the event handling code correctly so the different layers are informed at the right times when the filters change.

This seems a lot messier than the Stimulus solution — the simplest case would likely need at least four or five components defined, I don’t like the look of rendering rows via a plain function, and it looks like we’d need to define new components for each type of filter and layout. It’s likely possible to get by without these things, but you’d need to go past common React idioms.

A web component solution might be used as follows:

Because web components can inspect their children on startup, you can implement something like the ws-filters element to set up event handlers and collect filter information on its descendant input elements pretty easily.

This is quite a bit cleaner than React because you don’t need to create separate components for every type of layout or filter. On the other hand, it’s not as open-ended as Stimulus because much of the behavior and impact is encapsulated in the web components. However, I don’t like how it has to inspect its children to find e.g. the filter elements — Stimulus is less brittle in this regard.

In the course of studying this problem, I’ve gotten a better understanding of the strengths and weaknesses of these three philosophies. Each tool tries to stress what they think is important:

Honestly, I feel like a hybrid between two approaches works best in this case —creating a web component for the navigation, getting the nice encapsulation and easy HTML generation, and allowing Stimulus to sprinkle its behavior for the existing table and filters, since they can look like anything.

I’m far from an expert in any of these technologies, so it was a fun experiment!

Add a comment

Related posts:

13 Reasons Why

Projeto criado na aula de fotografia da professora Márcia Molina, com o propósito criar uma segunda temporada para a série 13 Reasons Why, na qual pessoas que já sofreram bullying são os modelos. As…

Vitamins and Supplements Mistakes and How to Avoid Them

Our diets in the modern age are becoming less and less likely to provide the vitamins and minerals we need for our health. Exhaustive farming techniques have stripped the nutrients from the soil…

Explaining the link between CAA and NRC

We keep hearing from those defending the Citizenship Amendment Act that it has nothing to do with the NRC, and that all the nationwide outrage is uncalled for. Understanding what happened in Assam…