Presentation is an important part of a UI component. Diffing makes it signifcantly easier to build well-encapsulated components, but styles still pose problems. CSS is global and hard to modularize. For simple cases, inline styles fix the problem easily:
var h = require('virtual-dom/h')
function render (state) {
var style = {
color: state.enabled ? 'red' : undefined
}
return h('p', {style: style}, 'Hello world!')
}
The only challenge around simple CSS is when using long lists with server-side rendering (solvable). Prefixing can be addressed via npm packages.
The more difficult cases are the features that can't be used in inline styles:
- @media queries
- @keyframe animations
- pseudo-selectors
matchMedia
to the rescue! Browser support is good and we get the benefit of using inline styles and JS instead of writing CSS.
We can integrate matchMedia
with a diffable UI as follows:
- Use an immutable data structure in your application like observ or Immutable.
- Use memoization to prevent unnecessary calls to your rendering functions (e.g. vdom-thunk).
- Create two immutatable states in your application: one for application state (e.g. user is logged in) and one for transient state (e.g. screen width < 500px).
- Use
matchMedia
with a set of predefined breakpoints (e.g. ergonomic-breakpoint) and update your transient state object in your handler (matchMedia(query).addListener(handler)
). - Pass your transient state into the render function of components, generally preceded by your application state.
- Render inline styles conditionally depending on the value of the matched media (transient state).
There are some solvable implementation challenges, including the need for a singleton delegator a la dom-delegator.
It's typically easier to get performant CSS animations versus pure JavaScript animation. If we can accomplish the following, we can stay modular while still using CSS:
- Build a CSS string in JavaScript, giving the keyframe a random name by appending a cuid
- Insert the CSS into a
<style>
tag - Export the keyframe name from the module
- require it
Example:
bounce.js
var bounce = createKeyframes({/* keyframes to bounce element */})
require('insert-css')(bounce.css)
module.exports = bounce.name
component.js
var bounce = require('./bounce')
function render () {
return h('button', {
style: {
animation: bounce + ' 2s infinite'
}
})
}
You should be able to easily avoid the use of virtually all pseudo-selectors with relatively simple JavaScript.
Take alternating row colors in a table:
tr {
background: white
}
tr:nth-child(even) {
background: gray
}
Instead:
function render (rows) {
return rows.map(function (row, index) {
return h('tr', {
style: {
background: index % 2 ? 'gray' : 'white'
}
}, row.map(renderColumn))
})
}
function renderColumn (data) {
// ...
}
Have to make em, just saying that they are easy. Would probably write them before publishing.
Because you don't want app state coupled to transient. If you decide to serialize and load both, it's easy enough. But when you build into state undo you don't want to undo window size changes. You certainly could, but that'd be hard and probably not desirable. Separation is a slight cost but can be a huge win if you avoid detangling.
10 components want to know when the screen size is <480px. You want to do that with one DOM listener, not 10. Then you trigger 10 handlers in JS land.
Only way to do semi-modular SASS: https://github.com/thlorenz/sass-resolve. Still not very modular.
You're building well-encapsulated components that can be used in multiple apps. Then you're coupling them to a bunch of global stylesheets. Your components are no longer encapsulated, and moving them between apps is likely to cause pain. That pain ranges from UI quirks at the best (layout bugs) and critical bugs at worst (e.g. a CSS hide/show that doesn't fire properly because a class name changes).