This post is an exploration of this hypothesis applied to 3 common CSS features:
- Media queries
I’ve also created an accompanying Github repo with full, working examples of all the snippets shown in this post.
While evaluating CSS-in-JS libraries for my current project recently, I did a deep dive on React Native for Web. The library’s philosophy and direction really resonated with me, and like many CSS-in-JS implementations it has many benefits over traditional CSS at scale. If you haven’t seen it yet check it out!
The Style FAQs in the documentation caught my eye:
“Media Queries may not be most appropriate for component-based designs. React Native provides the Dimensions API and onLayout props. If you do need Media Queries, using the matchMedia DOM API has the benefit of allowing you to swap out entire components, not just styles.”
“Pseudo-classes like :hover and :focus can be implemented with events (e.g. onFocus). Pseudo-elements are not supported; elements should be used instead.”
A “simple” button
Here is the example I’ll be using, a “simple” button component and its accompanying style sheet.
Goodbye pseudo-classes, hello setState
First up is the
:hover pseudo-class. One of the key tenants of React and other UI libraries is that they pull state out of the DOM and into your components so that it is easier to reason about. CSS Pseudo-classes are a direct contradiction to this advantage. In order for the button to change it’s background color, something must know whether or not the mouse is currently “hovering” over the button. The DOM is doing that. The DOM is managing an implicit state of “mouse is over the button” or “mouse is not over the button”. The component should be managing that state, not the DOM.
So, I’ll refactor the Button component to use the
onMouseLeave events and React’s
A more power media query
While pure CSS media queries are powerful, they tend to result in overriding of previously defined style values, which can often cause unwanted side effects and mistakes due to specificity errors. As the React Native for Web docs point out, they also are constrained to adjusting styles only. They can’t be used to swap out entire components.
Next, I’ll move the media queries out of the style sheet, preferring the
window.matchMedia DOM API instead. react-media is a nice little package that provides a component interface for
Animations as components
The final thing to tackle here is the
transition. For all but the most experienced CSS wizards out there, CSS animations or transitions can be really challenging. Moreover, they require the combination of multiple style rules working together.
In this case,
transition: background-color 0.5s describes a state change of the property
background-color. When the
background-color property of the button changes to another value, either through a class being added or removed, or a style property being edited, the background will shift from its previous value to its new value over a timespan of 0.5 seconds. It’s pretty complex, and this is a very simple transition.
Finally, I’ll bring the transition into the component with the react-transition-group package, which lets you describe a transition from one component state to another over time with a simple declarative API.
I can write tests for this stuff now!
Here are some of the tests:
This is what good real world React code often looks like. Nothing wrong with it being verbose and explicit. Don’t mistake terse for clean! https://t.co/qjB685FnuJ— Dan Abramov (@dan_abramov) November 17, 2017
Pros and cons
:hover. That will no longer have a visible effect. Instead, use the React dev tools to programmatically interact with your components.
I get really nervous when I see CSS or Sass that has a lot of nesting and overriding of style rules. I like how the final CSS file I ended up with was flat. All style rules are static, there is no nesting, and nothing is overridden by another class. Ahhhhh, peace.
I’m pretty excited about encapsulating common visual concerns into reusable components with a single responsibility. In this exploration I identified a reusable media query component and an animation component that could be extracted. In another project I’ve started extracting layout components such as
Boxto control spacing, and
Flexboxto control 1-dimensional layout. Check out these libraries for more: https://sapegin.github.io/react-spaceman/ and https://github.com/jxnblk/reflexbox.
Finally, I was not able to write a test for the transition that I was completely satisfied with. Transitions and animations are inherently time based, which does not play nicely with unit testing. The best I could do was take before and after snapshots and then rely on manual visual checks to make sure the transition looked proper while it was running. But, with a reusable
BgColorShiftcomponent now at my disposal, I can now trust it’s behavior, making future transitions easier to implement.