• Stars
    star
    193
  • Rank 201,081 (Top 4 %)
  • Language
    JavaScript
  • Created about 9 years ago
  • Updated almost 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Automatic Namespacing for your React className props

react-pacomo

React-pacomo transforms your component className props by prefixing them with a pacomo, or packageName-ComponentName- namespace. As a result, your component's CSS will be effectively locally scoped -- just like with CSS Modules, but without requiring a build step. React-pacomo also takes care of other common tasks like selecting classes and handling props.className.

React-pacomo's output is predicatable. This means that when you do want to override component CSS, you can. This makes it more suited for public libraries than inline style or CSS Modules.

For an example of react-pacomo in action, see the Unicorn Standard Starter Kit.

Installation

npm install react-pacomo --save

A simple example

Say you've got a NavItem component which renders some JSX:

class NavItem extends Component {
  render() {
    return <a
      href="/contacts"
      className={`NavItem ${this.props.active ? 'active' : ''}`}
    >
      <Icon className='icon' type={this.props.type} />
      <span className='label'>{this.props.label}</span>
    </a>
  }
}

While this works, it won't work if you ever import a library which defines other styles for .icon, .NavItem, etc. -- which is why you need to namespace your classes.

If your app is called unicorn, your namespaced component will look something like this:

class NavItem extends Component {
  render() {
    return <a
      href="/contacts"
      className={`unicorn-NavItem ${this.props.active ? 'unicorn-NavItem-active' : ''}`}
    >
      <Icon className='unicorn-NavItem-icon' type={this.props.type} />
      <span className='unicorn-NavItem-label'>{this.props.label}</span>
    </a>
  }
}

But while your styles are now safe from interference, repeatedly typing long strings isn't fun. So let's apply react-pacomo's higher order component. By using pacomoDecorator, the following component will emit exactly the same HTML and className props as the above snippet:

@pacomoDecorator
class NavItem extends Component {
  render() {
    return <a
      href="/contacts"
      className={{active: this.props.active}}
    >
      <Icon className='icon' type={this.props.type} />
      <span className='label'>{this.props.label}</span>
    </a>
  }
}

And just like that, you'll never have to manually write namespaces again!

Adding react-pacomo to your project

There are two methods for applying automatic namespacing to your components; a decorator function for component classes, and a transformer function used with stateless function components.

Neither of these methods are directly accessible. Instead, react-pacomo exports a withPackageName function which returns an object with decorator and transformer functions scoped to your package:

import { withPackageName } from 'react-pacomo'
const { decorator, transformer } = withPackageName('unicorn')

decorator(ComponentClass)

This function will return a new component which automatically namespaces className props within the wrapped class's render method.

Use it as a wrapper function, or as an ES7 decorator:

// As an ES7 decorator
@decorator
class MyComponent extends React.Component {
  ...
}

// As a wrapper function
var MyComponent = decorator(React.createClass({
  ...
}))

transformer(ComponentFunction)

This function will return a new stateless component function which automatically namespaces className props within the wrapped stateless component function.

const MyComponent = props => { ... }

const WrappedComponent = transformer(MyComponent)

Transformation Details

react-pacomo works by applying a transformation to the ReactElement which your component renders. The rules involved are simple:

Your root element receives the namespace itself as a class

The pacomo guidelines specify that your component's root element must have a CSS class following the format packageName-ComponentName.

For example:

let Wrapper = props => <div {...props}>{props.children}</div>
Wrapper = transformer(Wrapper)

Rendering <Wrapper /> will automatically apply an app-Wrapper CSS class to the rendered <div>.

className is run through the classnames package, then namespaced

This means that you use classnames objects within your className props!

For example:

@decorator
class NavItem extends Component {
  render() {
    return <a
      href={this.props.href}
      className={{active: this.props.active}}
    >
  }
}

If this.props.active is true, your element will receive the class app-NavItem-active. If it is false, it won't.

If your component's props contain a className, it is appended as-is

Since any className you to add your returned ReactElement will be automatically namespaced, you can't manually handle props.className. Instead, react-pacomo will automatically append it to your root element's className.

For example, if we used the NavItem component from above within a SideNav component, we could still pass a className to it:

@decorator
class SideNav extends Component {
  render() {
    return <div>
      <NavItem className='contacts' href='/contacts' active={true}>
        Contacts
      </NavItem>
      <NavItem className='projects' href='/projects'>
        Projects
      </NavItem>
    </div>
  }
}

The resulting HTML will look like this:

<div class='app-SideNav'>
  <a className='app-NavItem app-NavItem-active app-SideNav-contacts' href='/contacts'>
    Contacts
  </a>
  <a className='app-NavItem app-SideNav-projects' href='/projects'>
    Projects
  </a>
</div>

Child elements are recursively transformed

The upshot of this is that you can still use className on children. But keep in mind that huge component trees will take time to transform - whether you they need transforming or not.

While it is good practice to keep your components small and to the point anyway, it is especially important if you're using react-pacomo.

Elements found in props of custom components are recursively transformed

Not all elements you create will be appear under another element's children. For example, take this snippet:

<OneOrTwoColumnLayout
  left={<DocumentList className='contact-list' {...props} />}
  right={children}
/>

If we only scanned our rendered element's children, we'd miss the className on the <DocumentList> which we passed to left.

To take care of this, react-pacomo will also recursively transform elements on the props of custom React components. However, it will not look within arrays or objects.

Tips

You only need to call withPackageName once

As you'll likely be using the decorator or transformer functions across most of your components, you can make your life easier by exporting them from a file in your utils directory.

For an example, see utils/pacomo.js in the unicorn-standard-boilerplate project:

import { withPackageName } from 'react-pacomo'

export const {
  decorator: pacomoDecorator,
  transformer: pacomoTransformer,
} = withPackageName('app')

While decorator and transformer are easily understood in the context of an object returned by withPackageName, you'll probably want to rename them for your exports. My convention is to call them pacomoDecorator and pacomoTransformer.

Define displayName on your components

While react-pacomo can detect component names from their component's function or class name property, most minifiers mangle these names by default. This can cause inconsistent behavior between your development and production builds.

The solution to this is to make sure you specify a displayName property on your components. Your displayName is given priority over your function or class name, and it is good practice to define it anyway.

But if you insist that setting displayName is too much effort, make sure to tell your minifier to keep your function names intact. The method varies wildly based on your build system, but on the odd chance you're using UglifyJsPlugin with Wepback, the required configuration will look something like this:

new webpack.optimize.UglifyJsPlugin({
  compressor: {screw_ie8: true, keep_fnames: true, warnings: false},
  mangle: {screw_ie8: true, keep_fnames: true}
})

Use the LESS/SCSS parent selector (&)

While react-pacomo will prevent repetition in your JavaScript, it can't do anything about your CSS.

Luckily, since you're probably already using LESS or SCSS, the solution is simple: begin your selectors with &-.

In practice, this looks something like this:

.app-Paper {
  //...

  &-rounded {
    //...
  }

  &-inner {
    //...
  }

  &-rounded &-inner {
    //...
  }
}

Following the Pacomo CSS Guidelines

Namespacing your classes is the first step to taming your CSS, but it isn't the only one. The Pacomo System provides a number of other guidelines. Read them and use them.

Comparisons with other solutions

CSS Modules

Like react-pacomo, CSS Modules** automatically namespace your CSS classes. However, instead of runtime prefixing with React, it relies on your build system to do the prefixing.

Use CSS Modules instead when performance counts, you don't mind being a little more verbose, and you're not writing a library (where being able to monkey patch is important).

Pros

  • No runtime transformation (and thus possibly faster)
  • CSS class names can be minified (meaning less data over the wire)
  • Does not require a displayName when minifed

Cons

  • Depends on a build system
  • Does not handle props.className automatically
  • Does not append a root class automatically
  • Does not handle classnames objects
  • You don't know your class names ahead of time, meaning no monkey-patching

Inline Style

Inline Style takes a completely different approach, assigning your styles directly to the element's style property instead of using CSS.

While this may be useful when sharing code between react-native and react-dom, it has a number of drawbacks. Unless you're writing an exclusively native app, I recommend using react-pacomo or CSS Modules for your web app styles instead.

Pros

  • Styles can be re-used with react-native apps
  • Styles can be processed with JavaScript

Cons

  • Cannot re-use existing CSS code or tooling
  • Inline Style has the highest priority, preventing monkey patching
  • Media queries and pseudo selectors are complicated or impossible

FAQ

Isn't this just BEM?

No.

BEM goes further than react-pacomo by distinguishing between modifiers and elements.

For a BEM-based project, use something like react-bem.

Why should I use react-pacomo over BEM-based modules like react-bem?

Given most React components are very small and have limited scope, BEM is probably overkill. If you find your components are getting big enough that you think it might make sense to distinguish between elements and modifiers (like BEM), you probably should instead focus on re-factoring your app into smaller components.

Related Projects

More Repositories

1

create-react-blog

Start and deploy your own statically rendered blog with create-react-app
JavaScript
548
star
2

starter-kit

Project boilerplate using React, Redux and Uniloc
JavaScript
467
star
3

gulp-rev-replace

Rewrite occurences of filenames which have been renamed by gulp-rev
JavaScript
388
star
4

govern

Component-based state management for JavaScript.
TypeScript
266
star
5

javascript-cheatsheets

A collection of cheatsheets for JavaScript
HTML
177
star
6

webpack-black-triangle

A minimal webpack/ES6 project template with a spinning black triangle
JavaScript
166
star
7

memamug-client

Memamug helps you remember faces. Written with React, Maxim & Rails.
JavaScript
109
star
8

react-zen

React utilities for working with APIs
TypeScript
108
star
9

uniloc

Universal JavaScript Route Parsing and Generation
JavaScript
95
star
10

raw-react-part-1

Learn Raw React - No JSX, Flux, ES6, Webpack, ...
JavaScript
74
star
11

memamug-server

Memamug helps you remember faces. Written with React, Maxim & Rails
Ruby
68
star
12

use-codemirror

CodeMirror support for React
JavaScript
66
star
13

retil

The React Utility Library
TypeScript
46
star
14

maxim

Maxim provides a simple way to structure JavaScript applications, letting you focus on the parts that matter.
JavaScript
45
star
15

pacomo

A Method For Structuring Stylesheets in React-based Applications
42
star
16

cura-firebase-example

A React/Firebase starter with serverless SSR, routing and styled components.
JavaScript
29
star
17

reactjs.tokyo

Source for reactjs.tokyo. Built with universal-react-scripts and Firebase.
JavaScript
28
star
18

raw-react-part-2

Solution for Learn Raw React: Ridiculously Simple Forms
JavaScript
26
star
19

react-black-triangle

An opiniated React starter kit. Clone, follow the README, and have a working app in under two minutes.
JavaScript
26
star
20

sitepack

A JavaScript tool for building static web sites.
JavaScript
25
star
21

react-cx

Combine styles from CSS Modules with a `cx` prop.
JavaScript
24
star
22

react-base

Higher order component to handle merging callbacks, prefixing classnames and props passthrough.
JavaScript
21
star
23

raw-react-part-3

Learn Raw React - Routing
JavaScript
19
star
24

armo-breadboard

Themeable live coding for React.
JavaScript
16
star
25

popup-trigger

A helper for triggering popups on focus, hover, and selection.
TypeScript
15
star
26

memcord

Memcords let you pass Records as React props without breaking PureComponent.
TypeScript
14
star
27

mdx-loader

Webpack loader for MDX (i.e. JSX-infused Markdown) using mdx-it
JavaScript
14
star
28

react-typescript-firebase-navi

A starter kit to get you started faster than all the other starter kits πŸš…πŸ”₯πŸš€πŸ’
TypeScript
13
star
29

vouch.chat

JavaScript
11
star
30

derby-i18n

i18n support for derby.js
JavaScript
10
star
31

numbat-ui

CSS
10
star
32

use-promised-state

A state hook for React that doesn't whinge if used once used after unmount.
JavaScript
9
star
33

react-controllers

Utilities for creating React controller components
TypeScript
9
star
34

react-c

An implementation of Pacomo, a system to help structure your React stylesheets.
JavaScript
9
star
35

universal-react-app

A server-rendered starter forked from create-react-app
JavaScript
8
star
36

use-sx

Composable styles for React
TypeScript
8
star
37

node-joyo-kanji

A list of the Joyo Kanji packaged for node.js
8
star
38

react-routing-library

Concurrent routing that grows with your app.
TypeScript
7
star
39

firebase-react-demo

A simple demo of adding Firebase Functions/Hosting to a Create React App project
JavaScript
6
star
40

react-elemap

A tool for transforming React elements.
JavaScript
5
star
41

react-armory-helpers

Packages that can used with ES6 `import` within React Armory guides
4
star
42

static-directory-loader

Copy files into your public directory with a webpack import
JavaScript
4
star
43

react-junctions

JavaScript
3
star
44

meteor-yaml

Meteor smart package for YAML parsing/generation
JavaScript
2
star
45

mdx-cra-demo

JavaScript
2
star
46

junctions-react-app

A demo project using Junctions in the structure generated by Create React App
JavaScript
2
star
47

react-base-control

Higher order component to manage control-related events and state.
JavaScript
2
star
48

numbat-ui-example

JavaScript
1
star
49

angular-deputy

Resources with associations, computed values and offline support for AngularJS
CoffeeScript
1
star
50

m-and-c-in-mvc-talk

CSS
1
star
51

now-cura

Now builder for Create Universal React App
TypeScript
1
star
52

junctions-tutorial

Companion repo for the Junctions tutorial
JavaScript
1
star
53

armo

React stuff
JavaScript
1
star
54

vouch-landing

A landing page built in 2 hours with React and Firebase
JavaScript
1
star