Why you should use pure components
Tags: components, react
Published: 2020-03-27 02:31:32

I've seen a number of articles explaining what pure components are and tutorials about how to write them. I haven't seen as many good justifications for why you *should* consider structuring your components as pure components first. I'm hoping to make a good case for them.

## Impure Components *Tend* To Inhibit Use Cases

If you bundle a components state and behavior with its presentation layer, you risk obstructing important use cases.

As an example, let's say this minimal React toggle that maintains its own state is part of the component library your team uses:
```js
// Bear with me here.
const Toggle = (props) => {
  const [isOn, setIsOn] = React.useState(props.initialState);

  const handleToggle = () => {
    setIsOn(!isOn);
    props.onToggle(isOn);
  };
  return (<button onClick={handleToggle}>{`${isOn ? "on" : "off"}`}</button>);
}
```
What are the features of this toggle?
1. You can set an initial states
1. It maintains its own state
1. It informs you when the state changes

Then, let's say you're working on a UI that's going to let your user toggle a setting that might be costly. Your design team wants to make sure people aren't going to turn it on by mistake, so they want you to insert a confirmation before actually making the switch to the `on` state.

![Rough mockup for a toggle with a confirmation modal](https://dev-to-uploads.s3.amazonaws.com/i/vjdrbn3nsq8vpbfqwx0m.png)

This toggle implementation actually won't support this use case. There isn't a place to insert a dialog confirmation before switching the state of the toggle to `on`.

That toggle might be a little too contrived, so let's take a look at a real world component that was designed before declarative UIs caught on: [`dijit/form/ValidationTextBox`](https://dojotoolkit.org/api/?qs=1.10/dijit/form/ValidationTextBox) from version 1.10 of the [Dojo Toolkit](https://dojotoolkit.org/).

![Screenshot from the ValidationTextBox docs](https://dev-to-uploads.s3.amazonaws.com/i/woy4wqee2idw0wjvv5fa.png)

It's your standard text box, with some functionality that performs validation and displays valid states. I've copied some of its relevant parameter documentation here:

Parameter|Type|Description
-----|-----|-----
required|boolean|User is required to enter data into this field.
invalidMessage|string|The message to display if value is invalid.
missingMessage|string|The message to display if value is empty and the field is required.
pattern|string|This defines the regular expression used to validate the input.

You can see that they've tried to supply functionality to support a simple `required` prop to test if the text box contains a value, and a `pattern` prop to validate the text box's value with regular expressions.

Now, what sorts of use cases do these props not support?
1. Validation based on external values, e.g., is this value already present in a list of values you've entered prior?
1. Server-side validation, e.g. is this username taken?

In order to support #1, `ValidationTextBox` also allows you to override the `validator` function in a subclass, but if you look into the [source](https://github.com/dojo/dijit/blob/master/form/ValidationTextBox.js#L163) you'll find that the output of `validator` is used synchronously, meaning asynchronous validation, as in #2, might be impossible. As an aside, overriding `validator` means the `required` and `pattern` props will be ignored unless you explicitly use them in your custom `validator`.

Instead, imagine it exposed the property `isValid`, which would trigger valid or invalid styling. I'd bet you could deliver the equivalent functionality in less time than it would take you to even *understand* the current API, and could support those additional use cases.

## You Can Ship Those Behaviors On Top Anyway
Let's say you are convinced and rewrite your toggle component to be pure.

```js
const PureToggle = (props) => {
  return (<button onClick={() => props.handleClick()}>
    {`${props.isOn ? "on" : "off"}`}
  </button>);
}
```

But you don't want to throw away your hard work and you *really* want your consumers to not have to write those behaviors themselves. That's fine! You can also release those behaviors, in many forms including...

#### Pure functions
```js
const toggle = (previousState) => {
  return !previousState;
}
```

#### Hooks
```js
const useToggle = (initialState = false) => {
  const [isOn, setIsOn] = useState(initialState);
  return [isOn, () => {
/
    const nextValue = toggle(isOn);
    setIsOn(nextValue);
    return nextValue
  }];
};
```

#### Or Even A Higher Order Component!
```js
const ToggleComponentWithBehavior = (props) => {
  const [isOn, doToggle] = useToggle(props.initialState);
  return (<PureToggle
    isOn={isOn}
    handleClick={() => {
      const nextValue = doToggle();
      props.onToggle(nextValue);
    }
  }/>);
};
```

You might have noticed, but that higher order component actually exposes the exact same API as the original, behavior-coupled toggle implementation. If that's your ideal API, you can still ship it, and shipping the pure version will support the use cases you've missed.

## Takeaways

Now you might be thinking "OK, but I'm not writing a component library, I'm writing a product. The components I write have specific-case behavior so this doesn't apply to me." The underlying concept that I'm trying to convey is that separating your presentation from your behavior gives you more flexibility. That can still be beneficial when your components are only ever used once. When your behavior has to change in a way you didn't initially architect your component to support, your presentation layer can be in the best possible situation to be able to handle those changes.