Component Based Architecture

As the name suggests, styled-components allows us to think about styles as …components. 🤯 This requires a shift in how we approach CSS. It means we can take lessons learned from component architecture best practices and apply them. Max already wrote a great article on this, but I wanted to share some other component-y ideas I’ve been thinking about recently that build on it.

First, let’s start with an example. Take this ItemDisplay component:

Single responsibility

This component’s props suggest that it can have a bunch of different states, and the downside is that components like this encourage the developer to mutate it to the point that it might not reflect the original intention of the component. So although we can create components that are extremely flexible, that doesn’t necessarily mean we should, because it breaks the single responsibility principle:

The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility. — Wikipedia

Of course I’m not saying you should never write props for individual style rules, this is just a pattern I generally try to avoid and I’ve found that I can almost always come up with a better solution, like extending the component or using inline styles for very minor tweaks. An important thing to know about styled components is that they will always pass through known HTML attributes to the rendered markup, such as style . You always have that as an escape hatch.

Use inline styles for minor tweaks

Extend the component when you have more than a couple rules

A component should do one thing and should do it well. Keeping styled-components to ~50 lines or less has worked out well for us. When it grows any larger due to lots of different states, or targeting child elements, it’s best to keep the cognitive complexity to a minimum and decompose the component into smaller chunks.

Encapsulation

This is more of an OO concept, but I think it has implications here as well. A component represents an interface, which allows the developer to use it’s functionality without knowing about the nitty-gritty implementation details. It doesn’t expose details of internal functions, variables, or state.

That ItemDisplay component, however, exposes all of the implementation details. Encapsulation is a pretty powerful tool that lets us abstract the technical stuff to create a more human-friendly experience. Take advantage of it!

Reusability

A potential danger with component based architecture is that although it encourages reusability and single-responsibility, it can lead to other issues. If we design things to be too reusable it can end up making our code unreadable. I try to consider the reusability of a component carefully, and I’ve found it helpful to ask myself the following questions:

Is there already a primitive component that I can reuse or extend? Does the component have an extremely specific use case? Should it just be hard coded to serve that case? Does it need to be reused throughout the whole application? Does it need to be able to work in any context? (like when I’m designing something for our component library.)

In short, don’t default to making things reusable just for the heck of it, because there are tradeoffs. It’s not necessarily better to make things as reusable as possible.

At Decipher, we tend to keep primitive UI components in a separate library, App-wide components in a top-level directory, and one-off components near to the container components that render them.

Identify discrete UI states

Finally, when creating a component, it’s helpful to start by thinking through every state that a component needs to satisfy. For example, maybe that ItemDisplay component that has a default state and a primary state, and you need to derive styles based on those 2 states.

We can again draw a parallel here to regular ol’ React component development — the concept of core state v.s. derived state:

Core state — The minimal representation of state that you need to represent the UI.

Derived State — Any state that can be derived from the core state

To illustrate this, let’s look at a TodoList component. In state I would keep an array of all the todos. And then say you have to render some different UI based on the number of items in that array. Here’s a really bad way to do that:

👎

I’m pretty sure none of us would also keep the number of todo’s in state, especially because we would never mutate that number separately from the array. You would derive the number of todos by taking the length of the array. Having this “duplicate state” means you need to make sure to update both at the same time and keep them in sync at each step.

In styling land, I think of the parallel idea as considering your discrete UI states as your core state, (say, the primary state of our ItemDisplay ), and all of the style declarations that make up that UI state as derived. In other words, the derived state is made up of the individual style rules (height, width, margin, etc.). You shouldn’t be able to edit that derived state directly, since it’s dependent on the state of the UI. As developers it’s tempting to want full control over every single aspect of a component, resist the urge!

Here’s an example of what a more “state-oriented” ItemDisplay might look like. The implementation details (style rules) are encapsulated, and the only interface is a primary prop. I prefer to keep individual style rules uncluttered, which I think leads to cleaner, more readable code. I can see exactly what styles are applied in this component’s 2 discrete states:

In other words, style declarations should be “derived” from UI state.