Single Element Pattern (Singel) is a set of rules/best practices to create consistent, reliable and maintainable components in React and other component-based libraries. This is based on the idea that the building blocks of an application should resemble as most as possible native HTML elements. Read full article
This repo is a CLI tool for checking whether React components conform to the Singel pattern.
$ npm i -g singel
$ singel path/to/**/Component.js --ignore "path/to/**/ignored/Component.js"
Feel free to send a PR adding your open source project
// bad - 2 elements
const Element = props => (
<div {...props}>
<span />
</div>
);
// good
const Element = props => (
<div {...props} />
);
// good - if Element is good
const Element2 = props => (
<Element {...props} />
);
// good
const Element = props => (
<div {...props} />
);
// bad - will break if getId wasn't provided
const Element = ({ getId, ...props }) => (
<div id={getId()} {...props} />
);
// bad - will break if foo wasn't provided
const Element = ({ foo, ...props }) => (
<div id={foo.bar} {...props} />
);
// good
const Element = props => (
<div {...props} />
);
// bad - not rendering id
const Element = ({ id, ...props }) => (
<div {...props} />
);
// good
const Element = ({ id, ...props }) => (
<div id={id} {...props} />
);
// good
const Element = props => (
<div {...props} />
);
// bad - not rendering className
const Element = ({ className, ...props }) => (
<div {...props} />
);
// bad - not rendering style
const Element = ({ style, ...props }) => (
<div {...props} />
);
// bad - replacing className instead of appending
const Element = props => (
<div className="foo" {...props} />
);
// bad - replacing style instead of merging
const Element = props => (
<div style={{ padding: 0 }} {...props} />
);
// good
const Element = ({ className, ...props }) => (
<div className={`foo ${className}`} {...props} />
);
// good
const Element = ({ style, ...props }) => (
<div style={{ padding: 0, ...style }} {...props} />
);
// good
const Element = props => (
<div {...props} />
);
// bad - not passing onClick
const Element = ({ onClick, ...props }) => (
<div {...props} />
);
// bad - replacing onClick prop
const Element = props => (
<div {...props} onClick={myFunction} />
);
// good
const Element = ({ onClick, ...props }) => (
<div onClick={onClick} {...props} />
);
// good - it's ok to replace internal event handlers
const Element = props => (
<div onClick={myFunction} {...props} />
);
// good - calling internal and prop
const callAll = (...fns) => (...args) =>
fns.forEach(fn => fn && fn(...args));
const Element = ({ onClick, ...props }) => (
<div onClick={callAll(myFunction, onClick)} {...props} />
);
Say you have a Button
element and you want to display a Tooltip
when it's hovered. The first rule you'll want to break is rendering only one element. To handle that you have some options:
- Use CSS pseudo-elements (such as
:after
and:before
); - Create a non-singel element, which is fine;
- Nest styles instead of components.
Here's an example of how you can accomplish tha latter one:
/* could also be CSS-in-JS */
.button {
position: relative;
/* more button css */
}
.button:hover .tooltip {
display: block;
}
.button .tooltip {
display: none;
position: absolute;
/* more tooltip css */
}
const Button = ({ className, ...props }) => (
<button className={`button ${className}`} {...props} />
);
Button.Tooltip = ({ className, ...props }) => (
<div className={`tooltip ${className}`} {...props} />
);
Usage:
<Button className="my-specific-button">
<Button.Tooltip className="my-specific-tooltip">
😁
</Button.Tooltip>
Hover me
</Button>
Both Button
and Button.Tooltip
are single elements. You have all the benefits you would have by nesting them, but now with complete control over Button.Tooltip
from outside.
MIT © Diego Haz