Components
We classify components into four categories when building our apps:
- Higher Order Components (decorators): things such as Redux'
@connect
or redux-ui's@ui
- Reusable components: things such as SeatGeek's infinite scroll or an abstract pagination component
- Functional components: small, undecorated dumb components written as functions (see here for more info)
- Standard components: any other component that doesn't fit the above. Typically custom for your app
Higher Order Components (HOCs)
Higher order components are decorators that add powerful functionality to your standard components. These do things like connect to Redux' state store to automatically pass down props (@connect
), set up UI state for a component and pass down props (@ui
), or create a form with any validation (@reduxForm
).
A general rule of thumb is that a component should be higher order if it:
- Doesn't render directly to the DOM
- Passes down props to a standard component you're defining
- Hooks into react lifecycles to manage, manipulate, or improve a standard component
It's rare to need to build a higher order component; typically most components you build will directly render nodes to the DOM.
Building a higher order component that's abstract enough to reuse takes planning. It's often easier to write a HOC after you see recurring verbose patterns within your components.
Once you see patterns emerge you can plan to build a HOC that works for all use cases across your app.
Reusable components
Reusable components are different from higher-order components in that these are standard components that you add as JSX to render. Things such as an infinite list, pagination or tag list comprise a set of reusable UI components for use across your entire app.
There are a good suite of libraries for reusable components such as material-ui to speed up development.
The core of building good reusable components is thinking about the minimum amount of data that needs to be passed in for the component to work. When defining a tag list, for example, we could specify the propType API as:
class TagList extends Component {
static propTypes = {
tags: PropTypes.oneOfType([PropTypes.array, PropTypes.object).isRequired,
max: PropTypes.number
}
static defaultProps = {
max: 10
}
}
Reusable components should always be saved in the components
directory of your source.
Functional components
Functional components are lightweight functions that return a JSX tree. They're suited for small components such as single elements or groups of small DOM nodes.
An example of a shared Button functional component:
import React from 'react';
import css from 'react-css-modules';
import styles from './button.css';
/**
* Button represents a default button which accepts three props: text,
* onClick and variant.
*
* Note that this Button needs styles applied through react-css-modules;
* to use default styles declared here import the 'Button' component.
*/
const UnstyledButton = ({ text, onClick, variant }) => (
<Button
styleName={ styles[variant] }
onClick={ onClick }>{ text }</Button>
);
// Note: 'styleName' is react-css-module's version of the 'className' prop
// Create a Button with the default styles applied
const Button = css(Button, styles);
export default Button;
export UnstyledButton;
You'll see a few awesome things about functional components:
- They're super concise, which means they're easy to read and write
- They define all of their props using destructuring as opposed to PropTypes. This is awesome as they're self-documenting... all props are included in the function signature along with defaults
Because functional components are so small they're also great to use for building shared components. We'll talk about this more in the 'sharing components' chapter, though the above is a great example of a shared component.
Standard components
Standard components represent the core of your app. These are typically single-purpose components designed and built for a particular route in your app.
Standard components have no internal state — they receive state as props through a combination of Redux' @connect
and reselect.
They are typically stored within a folder representing the current route, for example scenes/pages/index.js
.