Kill hidden dependencies with React styled-components

According to the React documents:

Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.

That makes sense to me. One of my favorite things about React is that it encourages you to state dependencies clearly by explicitly importing only the modules you need.

Let’s take a look at a <ControlPanel/> component. It appears on page two of a wizard whose job is to display the selections from a few dropdowns.

<ControlPanel/> in the wild
control_panel.js

I know <ControlPanel/> imports <Button/>, a child component. It also needs a date formatting helper that we wrote. There is Underscore, a third-party library. Heck, we even need to import React itself explicitly.

These declarations help me think about the business logic of the <ControlPanel/> in isolation from its dependencies. There are no magic globals. If I need to dig deeper to understand an imported module, the path to the source is right there. And if I feel good, I can just take the dependencies at face value and focus on the component’s job — to take in props and spit out markup.

Styling should be reusable too

<ControlPanel/> is reusable in the sense that it should return the same markup for a given set of inputs, no matter where I drop it within my app. At least that’s the idea. But styling is a hidden dependency we’re not declaring. Rather, there’s a `.control-panel` class, which appears on a `new-hires.css.scss` stylesheet. The name is a reference to the feature in which the component was first used.

This is the pattern we’ve followed at Greenhouse from the beginning — create separate, feature-specific stylesheets that roll up into a single application.css.scss. The conventional wisdom is that a stylesheet is more maintainable than inline styles. SASS features like variables enable you to update a color definition easily at the source, rather than chase down scattered hex values. And you separate concerns — styles are in their own file, away from their component’s business logic.

But this pattern creates a host of problems. For one, the code separation is cosmetic. All the individual stylesheets roll up into that single application.css.scss. So it’s easy to overwrite styles, based on the arbitrary order in which you import stylesheets into the manifest.

To avoid the global scope, it’s common to use CSS nesting. This can be pretty awkward. The other night, I was happy to discover that <ControlPanel/> fit perfectly into my feature. Reusable React components for the win!

And the logic did work exactly as I wanted, out of the box. Trouble was, the styling looked completely different in my feature than in its original page. Why? The component styling was through that `.control-panel` class. But that’s a little generic, so we namespaced the stylesheet as `.new-hires-container .control-panel`, a reference to the component’s original parent.

So now this component is not reusable at all; you need to add a CSS class to its parent to get consistent styling.

Styled-components have reusable logic AND styling

I solved this problem by refactoring <ControlPanel/> using the styled-components React library. If you’re new to the project, check out its awesome docs, including a really nice talk by co-founder Max Stoiber.

The library helps you write components you can drop in anywhere, knowing their styles will behave deterministically. In short, it deeply cuts your risk of polluting the global scope. Well there is a helper called injectGlobal, that the styled-components docs preface with We do not encourage the use of this. So there.

Let’s look at how I refactored <ControlPanel/>

control_panel.js

The important part is the new <StyledDiv/> component. Notice we got rid of the `.control-panel` class. There are no more stylesheets. No dependencies on classes in a parent container. Now, the styling of this component is clear to any developer, and the component will behave deterministically wherever we drop it.

And we still get the benefits of SASS! For example, the color variables rely on another library called sass-variable-loader that parses our SASS color names file into a JavaScript object. This is the `Utilities/colors` file that we’re importing to <ControlPanel/>

utilties/colors.js

Styled-components at Greenhouse

Greenhouse engineering has made a big effort this year to up our front-end game. Our old JavaScript was pretty tough to work in; it’s a patchwork of jQuery, Angular 1.x and Backbone / Marionette. There were lots of magical global imports, and very few reusable components.

But a year ago, we hired an experienced front-end engineer, who fleshed out a team and helped us rally around React and Webpack.

Although we have a lot of legacy JS (and a lot of legacy stylesheets), all our new front-end code is written in React. And we’re trying to make styled-components the rule.

The Proclaimers were ahead of their time

We’re even working toward a shared component library that any new project could access out of the box. styled-components will be key here, in order to guarantee that <ControlPanel/> looks and feels the same on any page of any Greenhouse product.