React with CSS-modules
(or how I learned to stop using inline styles)
Ramsay Lanier
First, Why are inline styles a thing?
Scoped Styling
CSS is globally scoped. Styles are accessible anywhere. That means that changing a style could have unintended consequences. Or you have become increasingly more specific with your styles.
Which leads to...
Specificity Conflicts
Image courtesy of Smashing Magazine
DEAD CODE
We've all seen this before. This case is a minor infraction, but in the aggregate it can be harmful.
These are all really bad things and should be dealt with
But we got problems
Like
hover states?
pseudo selectors?
class composition?
Sass stuff?
enter radium
Radium is cool
var Radium = require('radium');
var React = require('react');
var color = require('color');
@Radium
class Button extends React.Component {
static propTypes = {
kind: React.PropTypes.oneOf(['primary', 'warning']).isRequired
};
render() {
// Radium extends the style attribute to accept an array. It will merge
// the styles in order. We use this feature here to apply the primary
// or warning styles depending on the value of the `kind` prop. Since its
// all just JavaScript, you can use whatever logic you want to decide which
// styles are applied (props, state, context, etc).
return (
<button
style={[
styles.base,
styles[this.props.kind]
]}>
{this.props.children}
</button>
);
}
}
// You can create your style objects dynamically or share them for
// every instance of the component.
var styles = {
base: {
color: '#fff',
// Adding interactive state couldn't be easier! Add a special key to your
// style object (:hover, :focus, :active, or @media) with the additional rules.
':hover': {
background: color('#0074d9').lighten(0.2).hexString()
}
},
primary: {
background: '#0074D9'
},
warning: {
background: '#FF4136'
}
};
but how would you replicate something like :first-of-type?
like this?
var Radium = require('radium');
var React = require('react');
var color = require('color');
@Radium
class View extends React.Component {
render() {
const { buttons } = this.props;
return (
{buttons.map( (button, index) => {
const style = index === 0 ? styles.first : styles.base;
return (
<button
style={style}>
{this.props.children}
</button>
)
})}
);
}
}
var styles = {
base: {
color: '#fff',
':hover': {
background: color('#0074d9').lighten(0.2).hexString()
}
},
first{
background: 'green',
':hover': {
background: color('green').lighten(0.2).hexString()
}
}
};
With CSS Modules
var React = require('react');
import CSSModules from 'react-css-modules';
import styles from './button.scss';
@CSSModules(styles, {allowMultiple: true})
class View extends React.Component {
render() {
const { buttons } = this.props;
return (
{buttons.map( button => {
return (
<button styleName="base">{button}</button>
)
})}
)
}
}
//buttons.scss
.base{
color: white
&:hover{
background-color: lighten(green, .2);
}
&:first-of-type{
background-color: lighten(red, .2);
}
}
this is hard to swallow.
react css modules are so much better.
The setup
WEbpack
Node
Express
Webpack - Development
'use strict';
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
const devServer = {
contentBase: path.resolve(__dirname, './app'),
outputPath: path.join(__dirname, './dist'),
colors: true,
quiet: false,
noInfo: false,
publicPath: '/',
historyApiFallback: false,
host: '127.0.0.1',
port: 3000,
hot: true
};
module.exports = {
devtool: 'eval-source-map',
debug: true,
devServer: devServer,
entry: [
'webpack/hot/dev-server',
'webpack-hot-middleware/client?reload=true',
path.join(__dirname, 'app/main.js')
],
output: {
path: path.join(__dirname, '/dist/'),
filename: '[name].js',
publicPath: devServer.publicPath
},
module: {
loaders: [
{
test: /\.js?$/,
loader: 'babel',
exclude: /node_modules|lib/,
},
{
test: /\.json?$/,
loader: 'json'
},
{
test: /\.css$/,
loader: 'style!css?modules&localIdentName=[name]---[local]---[hash:base64:5]'
},
{
test: /\.scss$/,
loaders: [
'style?sourceMap',
'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]',
'sass?sourceMap'
],
exclude: /node_modules|lib/
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: 'app/index.tpl.html',
inject: 'body',
filename: 'index.html'
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('dev')
})
],
node: {
fs: 'empty'
}
};
webpack.config.js
Webpack - Production
'use strict';
var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var StatsPlugin = require('stats-webpack-plugin');
module.exports = {
entry: [
path.join(__dirname, 'app/main.js')
],
output: {
path: path.join(__dirname, '/dist/'),
filename: '[name]-[hash].min.js'
},
plugins: [
new ExtractTextPlugin('/app.min.css', {
allChunks: true
}),
new HtmlWebpackPlugin({
template: 'app/index.tpl.html',
inject: 'body',
filename: 'index.html'
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false,
screw_ie8: true
}
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
],
module: {
loaders: [
{
test: /\.js?$/,
loader: 'babel',
exclude: /node_modules|lib/,
},
{
test: /\.json?$/,
loader: 'json'
},
{
test: /\.css$/,
loader: 'style!css?modules&localIdentName=[name]---[local]---[hash:base64:5]'
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]!sass'),
exclude: /node_modules|lib/,
},
],
}
};
webpack.production.config.js
Node w/ express
/* eslint no-console: 0 */
import path from 'path';
import express from 'express';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import webpackMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';
import config from './webpack.config.js';
const isDeveloping = process.env.NODE_ENV !== 'production';
const APP_PORT = isDeveloping ? 3000 : process.env.PORT || 3000;
let app = express();
if (isDeveloping) {
const compiler = webpack(config);
app = new WebpackDevServer(compiler, {
hot: true,
historyApiFallback: true,
contentBase: 'src',
publicPath: config.output.publicPath,
stats: {
colors: true,
hash: false,
timings: true,
chunks: false,
chunkModules: false,
modules: false
}
});
app.use(webpackHotMiddleware(compiler));
} else {
app.use(express.static('./dist'));
app.get('*', function response(req, res, next) {
res.sendFile(path.join(__dirname, '/index.html'));
});
}
app.listen(APP_PORT, () => {
console.log(`App is now running on http://localhost:${APP_PORT}`);
});
server.js
example time
react cssmodules sass boilerplate on github
import React from 'react';
import ReactDOM from 'react-dom';
import styles from './App.scss';
class App extends React.Component {
render() {
const { children } = this.props;
return (
<div className="application">
{children}
</div>
)
}
}
export default App;
@import "./styles/reset";
@import "./styles/fonts";
@import "./styles/vars";
@import "./styles/typeplate/typeplate";
@import "./styles/z-index";
*{
box-sizing: border-box;
}
body{
font-family: $sansSerif;
font-weight: 400;
background-color: darken(white, 10%);
}
a{
text-decoration: none;
}
import React from 'react';
import Page from './page.js';
import Section from '../sections/section.js';
class HomePage extends React.Component{
render(){
return (
<Page>
<Section title="Ramsay" type="primary"></Section>
<Section title="Work" type="secondary"></Section>
<Section title="Play" type="dark"></Section>
<Section title="Hire" type="tertiary" backgroundImage="http://red-badger.com/blog/wp-content/uploads/2015/04/react-logo-1000-transparent.png"></Section>
</Page>
)
}
}
export default HomePage;
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './section.scss';
@CSSModules(styles, {allowMultiple: true})
class Section extends React.Component{
_renderPostTitle(){
const title = this.props.title.match(/.{1,2}/g);
return title.map( (fragment, index) => {
return <span key={index}>{fragment}<br></br></span>
})
}
render(){
const { type, backgroundImage } = this.props;
const bg = {
backgroundImage: "url('" + backgroundImage + "')",
};
const bgClass = backgroundImage ? 'with_background' : '';
return(
<div styleName={type + ' ' + bgClass} style={bg}>
<h2 styleName="title">{this._renderPostTitle()}</h2>
{this.props.children}
</div>
)
}
}
export default Section;
.base{
composes: child_1 container justify_center from '../../styles/flexbox.scss';
position: relative;
}
.primary{
composes: base;
composes: priamry primary_bg from '../../styles/colors.scss';
}
.secondary{
composes: base;
composes: secondary_bg from '../../styles/colors.scss';
}
.dark{
composes: base;
composes: dark_bg from '../../styles/colors.scss';
}
.tertiary{
composes: base;
composes: tertiary_bg from '../../styles/colors.scss';
}
.with_background{
composes: cover center from '../../styles/background.scss';
position: relative;
&:after{
background-color: fade-out(black, .65);
}
}
.title{
composes: strong uppercase center bold from '../../styles/typography.scss';
composes: self_center from '../../styles/flexbox.scss';
// background-color: fade-out(white, .2);
border: 10px solid fade-out(black, .8);
color: white;
font-size: 6vw;
line-height: 5vw;
padding: 1rem 2rem;
}
QUESTIONS
React with CSS Modules
By Ramsay Lanier
React with CSS Modules
Stop using inline styles. They're whack.
- 1,777