-
Notifications
You must be signed in to change notification settings - Fork 3
Component Principles
Below are a few principles to consider when building reusable and highly composable React components. There are always exceptions, so these should be considered guidelines rather than rules (but exceptions should have very good reasons).
While you can get away with a lot of UI elements being stateless functional components, the moment you need to tie into the React component lifecycle, or organizing blocks into methods...you end up having to rewrite the component structure anyway. To avoid this, I find it much more useful to just start by extending React.PureComponent
from the start.
A component should be able to exist without any props set, in a default state.
If the option is going to be one or the other, don't allow both. Utilize enums when there are options instead of flags - if two things being true at the same time doesn't make sense, we shouldn't do it.
This one doesn't really need an explanation, but for the sake of formality:
<Clickable onClick={doSomething} />
While this can seem weird, it's for compatibility. When adding something new, previous instances won't have that prop value set (undefined/falsey). Defaulting the value to false will mean that existing uses remain the same while adding new functionality.
Composition is arguably components' strongest attribute. We should reach for this so that we can create more abstract, flexible, and composable components.
If you need to pass functionality/scope from one component to another, do it with a render prop instead of higher order components. It's more explicit.
Instead of passing an array of objects to be rendered by the component, perhaps it makes more sense to pass the actual DOM elements to be rendered with any context that might be necessary to render them (the main reason you'd pass data to be rendered by the component).
If it's obvious that what you're building will only have one render prop, use children
. But in all other cases, preface your prop name with render
.
<HoverContainer
renderOver={(props) => (<a />)}
renderOut={(props) => (<span />)}
/>
Overrides on a per case basis are inevitable, so if a simple utility class on the element would do the trick we should make that easy (as opposed to adding a class to a wrapper and having to override with custom CSS).
Because we are building more than just a component library, but a style library as well, we need to consider that the way we style components should not rely on our React implementation. Instead of maintaining two approaches (both in CSS and JS) we should strive to reach for our styles living solely in CSS and accessing them via classes.
For all components that are intended to be used with Redux Form (basically, any input we intend to change the values of a form) should follow a few key ideals.
This is just to easily identify which components are meant to interact with Redux Form. So if you're building an Address
component, there should be a corresponding AddressFields
that wraps Address
in a Fields
component (from Redux Form). If you're building a Toggle
component, the corresponding wrapped component RadioField
that is wrapped in Field
.
There are many props that you won't need to make use of, but there are a few that we should use to simplify the interactions between components and Redux Form (input.value
and input.onChange
);
class Toggle extends PureComponent {
render() {
const {
children,
input: { value, onChange },
} = this.props
return (
<a className="toggle-container" onClick={() => onChange(!value)}>
<span className="toggle">{value ? 'On' : 'Off'}</span>
{children}
</a>
)
}
}
This is just extremely useful for not having to specific which props should get passed through, and having to constantly add to the list later down the road.
class ToggleField extends PureComponent {
render() {
return (
<Field {...this.props} component={ToggleField} />
)
}
}