Skip to content
This repository has been archived by the owner on Feb 26, 2022. It is now read-only.

Component Principles

Justin Jones edited this page Jul 11, 2018 · 9 revisions

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).

React.PureComponent

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.

Props

Zero props = default state

A component should be able to exist without any props set, in a default state.

Enums over flags

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.

Event props start with on

This one doesn't really need an explanation, but for the sake of formality:

<Clickable onClick={doSomething} />

Boolean props default to false (name accordingly)

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.

Utilize {children} when possible

Composition is arguably components' strongest attribute. We should reach for this so that we can create more abstract, flexible, and composable components.

Render Props

Render props over HOCs

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.

Render props over configuration

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).

Render props start with render

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 />)}
/>

Styles

Inherit classNames

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).

Avoid Inline CSS / CSS in JS

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.

Third-party Interactions

Redux Form

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.

Component name contains Field or Fields suffix

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.

Form components utilize Field props

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>
    )
  }
}

Wrapped components get all props passed through

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} />
    )
  }
}