Skip to content

Instantly share code, notes, and snippets.

@theengineear
Last active April 8, 2017 00:43
Show Gist options
  • Save theengineear/6e6e1337d996feb25eb11fccbe27c3fc to your computer and use it in GitHub Desktop.
Save theengineear/6e6e1337d996feb25eb11fccbe27c3fc to your computer and use it in GitHub Desktop.
Standardized method of styling React components to promote more flexibility in reusable components.
import React, { Component, PropTypes } from 'react';
import { StyleSheet, css } from 'aphrodite';
class MyComponent extends Component {
computeStyle() {
return {
// Dynamically generated style that cannot be simply enumerated
// in a sheet.
};
}
render() {
// We *always* accept `sheets` and `styles` props which are arrays of
// custom sheets and styles, respectively, that the caller can supply.
const { sheets: customSheets, styles: customStyles } = this.props;
// The common thing to do is merge some props from the same sheets or
// the same styles. The goal here is two force ourselves to always
// consider the caller's styling needs.
const sheets = [sheet, ...customSheets];
const styles = [this.computeStyle(), ...customStyles];
// Here, note that we can easily use our opinionated sheet and
// dynamically-computed style while including custom overrides.
return (
<div className={mcss(sheets, 'base')} style={style(styles, 'base')}>
<div
className={mcss(sheets, 'text')}
style={style(styles, 'text')}
>
{'I can has stylez?'}
</div>
</div>
);
}
}
MyComponent.propTypes = {
sheets: PropTypes.array,
styles: PropTypes.array
};
export default MyComponent;
// styles.js
// Utilities to make applying multiple styles/sheets simpler.
// Deeply flattens an array.
// [[A], [B, C, [D]]] -> [A, B, C, D]
function flatten(list) {
return list.reduce((r, x) => r.concat(Array.isArray(x) ? flatten(x) : x), [
]);
}
// Pull props out of objects and only returns an array of *truthy* values.
function _map(...args) {
let nestedArray;
if (args.any(arg => typeof arg === 'string')) {
// Multiple objects for all props --> `_map(objects, 'base', 'hover')`.
const [objs = [], ...props] = args;
const trueProps = props.filter(Boolean);
const trueObjects = objs.filter(Boolean);
nestedArray = trueProps.map(prop => trueObjects.map(obj => obj[prop]));
} else {
// Per-prop objects --> `_map([obj0, 'base'], [obj1, 'hover'])`.
nestedArray = args.map(
([objects = [], prop]) =>
prop ? objects.filter(Boolean).map(obj => obj[prop]) : null
);
}
return flatten(nestedArray).filter(Boolean);
}
// A utility to make applying multiple styles simpler.
function style(...args) {
// Optionally do some validation here... E.g., no nested styles or ':hover'.
const objects = _map(...args);
return Object.assign({}, ...objects);
}
// A utility to make applying multiple sheets simpler (multiple-css).
function mcss(...args) {
return css(_map(...args));
}
// MyComponent.css.js
// Where there are two exports:
// export styleObject;
// export default styles;
const styleObject = {
base: { width: '100px', height: '100px', color: 'red' },
text: { font: '16px', ':hover': { color: 'blue' } }
};
const sheet = StyleSheet.create(styleObject);
// Caveats. There are always caveats.
//
// 1. This basically binds users of this lib to Aphrodite. I think this is OK as
// long as we expose Aphrodite's `StyleSheet.create` function as a utility
// function so folks will not need to install `aphrodite` as a first-order dep.
//
// 2. Dynamically changing values are not well-handled by `aphrodite`. I.e., if
// you want to have a `{':hover': {color: getRandomColor()}}`... too bad. Note
// that this particular example cannot be handled by inlining since React does
// not support `:hover` either in `style={{ .. }}`.
//
// 3. If we *do* have dynamically changing values that *are* valid, I'd suggest
// that we *not* try to make a special spec out of it, but rater let the
// developer of the component choose a prop name that will suffice.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment