So you’ve got a React application, and you want to style it. But no matter how hard you try, you just can’t get excited about the existing options.
Maybe you like how Inline Style eliminates globals, but don’t want to gamble on an untested technology which doesn’t play well with others. Or maybe you like the concept behind CSS Modules, but feel they are too heavyweight for your own application.
Wouldn’t it be great if you could have the ease-of-use of Inline Style with the compatibility of CSS Modules? Say, something which automatically prefixes className
props with a unique namespace? Actually, react-pacomo does exactly that. Build-process free. Without any modifications to your existing components. Almost like magic.
See react-pacomo in action in the Unicorn Standard Starter Kit.
Pacomo Namespacing
The key to react-pacomo is the pacomo, or packageName-ComponentName-className
namespacing convention.
But why pacomo? Well, given that your package and component names are unique, CSS class names which are prefixed with them will also be unique. And importantly, this stays true even when code is reused across applications and organisations.
Let’s have a look at an example. If you were to write a NavItem
component in raw HTML using the pacomo conventions, it’d look a little like this:
<a href="/contacts" class="app-NavItem app-NavItem-active">
<span class='app-NavItem-icon'>contacts</span>
<span class='app-NavItem-label'>Contacts</span>
</a>
But while namespaces eliminate problems caused by globals, long class names are an issue by themselves. And while the LESS/SCSS parent selector will solve this for you on the CSS side, it won’t magically shorten the class names in your JSX. And that’s where react-pacomo comes in!
Transforming your Components with react-pacomo
The best way to understand how this works is with an example, so let’s start by rewriting the above NavItem
component with JSX:
var NavItem = React.createClass({
render: function() {
return <a
href="/contacts"
className={`app-NavItem ${this.props.active ? 'app-NavItem-active' : ''}`}
>
<span className='app-NavItem-icon'>{this.props.type}</span>
<span className='app-NavItem-label'>{this.props.label}</span>
</a>
}
})
When you use your NavItem
component in another component’s render
function, what actually happens is that under the hood, JSX calls React.createElement
to create a ReactElement
object:
// Returns a `ReactElement`
React.createElement(NavItem, {type: 'contacts', label: "Contacts"})
To cut a long story short, it is possible to transform this ReactElement
. react-pacomo implements a transform which prefixes your className
props with namespaces based on your component’s displayName
. And thus, the above JSX reduces to this:
var NavItem = pacomoDecorator(React.createClass({
render: function() {
return <a href="/contacts" className={this.props.active ? 'active' : null}>
<span className='icon'>{this.props.type}</span>
<span className='label'>{this.props.label}</span>
</a>
}
}))
But the generated HTML still looks like this:
<a href="/contacts" class='app-NavItem app-NavItem-active'>
<span class='app-NavItem-icon'>contacts</span>
<span class='app-NavItem-label'>Contacts</span>
</a>
And walla, you can now have your cake and eat it too! Your React Component definitions are simple, you can re-use existing CSS styles and tools, and you don’t even have to resort to build systems like Webpack!
Public Service Announcement: I actually announced this to my Newsletter subscribers a week ago. If you don’t want to miss out on other useful tools, enter your email below! And to make a good deal even better, you’ll immediately receive three print-optimised cheatsheets on React and ES6 — for free!.
Be the first to hear about new tools
Other things react-pacomo can do
While automatic namespacing is the main game, react-pacomo makes your life easier in other ways too.
className
values are passed through the classnames package
If you haven’t used the classnames package before, then click that link right now and learn what you’ve been missing out on.
Then, rejoice in the knowledge that you can now do things like this:
var NavItem = pacomoDecorator(React.createClass({
render: function() {
return <a href="/contacts" className={{active: this.props.active}}>
...
</a>
}
}))
It works with stateless component functions and ES7 decorators
While vanilla React.createClass
is supported, you’ll probably want to use react-pacomo like this:
@pacomoDecorator
export default class NavItem extends React.Component {
render() {
return <a href="/contacts" className={{active: this.props.active}}>
...
</a>
}
}
Or like this:
const NavItem = ({active}) =>
<a href="/contacts" className={{active}}>
...
</a>
export default pacomoTransformer(NavItem)
It automatically handles props.className
If you pass a className
to a DOM component, it just works. But if you pass a className
to a custom component without manually handling it, nothing happens.
That is, nothing happens unless you’re using react-pacomo. If you are, it will be automatically appended to your component’s root element’s className
.
I’m a Believer! How do I set this up?
First you install it:
npm install react-pacomo --save
Then, assuming you’re using ES6, you add a file which tells react-pacomo your package name. You then exports functions for decorating and transforming your package’s components:
import { withPackageName } from 'react-pacomo'
export const {
decorator: pacomoDecorator,
transformer: pacomoTransformer,
} = withPackageName('app')
Finally, decorate or transform your components with pacomoDecorator
or pacomoTransformer
. Since you’re already using className
attributes, it is as simple as that. Really!
So James, do you have any more tips for making CSS bearable?
While namespacing is critical to ensuring that your CSS is maintainable, there are many other tools you can use to build on this foundation.
First, if you haven’t done so yet, make sure you’re using LESS or SCSS. This will allow you to use the parent selector to avoid repetition in your stylesheets themselves.
But assuming you’re already using LESS or SCSS, the next step is to decouple your component stylesheets. What do I mean by this?
Decoupling component stylesheets
Single Page Applications are often structured with a single main.scss
which imports variables, mixins, and component stylesheets. This makes it possible for component stylesheets to easily use the application’s mixins and variables. It also ties them to the application, impeding reusability.
In contrast, independent stylesheets can be compiled individually. They import their own variables and mixins, making dependencies explicit and easier to reason about. But more importantly, independent stylesheets make for independent components — reusable across projects, style included. And with a little webpack magic, independent stylesheets can be imported within your component .jsx
files and still distributed as a single .css
file.
But now for the million dollar question: how do you set this up?
Well, you could spend hours and hours with webpack documentation trying to figure it out yourself. Or you could just join my newsletter to get notified when I finish the guide. And to sweeten the deal, in return for your e-mail you’ll immediately receive three print-optimised PDF cheatsheets. One on React (see preview), another for ES6 and yet another for JavaScript promises. All for free!
I will send you useful articles, cheatsheets and code.
One more thing – I love hearing your opinions, questions, and offers of money. If you have something to say, send @james_k_nelson a tweet, or send me an e-mail at james@jamesknelson.com. I’m looking forward to hearing from you!
Read More
- Why You Shouldn’t Use Inline Style with React
- Learn Raw React
- React Higher Order Components
- LESS Parent Selector
Is there anyway to use the pacomoDecorator for all the React components, without adding an annotation to all the classes? I can understand that it might be problematic if you want to switch progressively to this convention, but if you start a project from the very beginning this annotation seems like repeating to me.
Sorry if I am overseeing something cirtical, but I am just about to get started with React.