React Components: Best Practices — part 1

Controlled Components with Children

React Components come in different shapes and sizes. In this article I’ll list various things to think about, when writing a React Component.

Please note: This is not a tutorial, you should know the component lifecycle, a bit of Redux and how this.props.children work before looking at this article

Let’s start with a simple dropdown component:

Dropdown Button

Component Signature:

<Dropdown options={optionsObject} selected={value} />

Limitations

This works in some cases, but there are limitations which prevent us from using this component in different situations:

  1. we can’t programatically know whether the dropdown is open or not. In other words, if I want to open this dropdown when a user clicks a different button on the page, I can’t.
  2. hard to immediately tell what the optionsObject is: can I safely assume it’s a key-value pair like {optionName: optionValue}? Or is it {option: {name: 'optionName', value: 'optionValue', ...}} ?
  3. Can’t change or modify the contents of the dropdown

Improvements

<Dropdown
open={this.state.dropdownOpenState}
onClick={this.actions.toggleDropdown()}
title={this.state.selectedOption}
>
<DropdownOption
name="HTML"
onClick={this.actions.selectOption("HTML")}
>
HTML
</DropdownOption>
...
</Dropdown>

I usually refer to this as a Fully Controlled component. This is because it behaves like a pure function, in that the UI doesn’t change unless the props change first. You fully control what is being displayed, regardless of what the user does.

And you can choose to implement this component as a pure javascript function, or an ES6 class, but the idea is the same: the UI doesn’t change unless the props change first.

Notice how we addressed the limitations:

  1. The dropdown is open whenever we set the prop open to true, and closed when open is false. This means that if I want to open the dropdown when pressing a different button, I now have a way to do it.
  2. We no longer use an optionsObject prop, so we don’t need to agree on a format
  3. I can now easily change the contents of the dropdown to use checkboxes if I want to, add buttons and anything else I want.

And then, building on top of the initial Dropdown, I now can build some pretty cool stuff:

Example of a complex dropdown

As an example, here’s some pseudocode for what I’d expect the Component Signature to look like:

<Dropdown
open
title="Emma Stone"
onClick={this.actions.toggleDropdown}
>
<Checkbox
onClick={this.actions.toggleOption('Ryan Gosling')}
checked={false}
>
Ryan Gosling
</Checkbox>
<Checkbox
onClick={this.actions.toggleOption('Emma Stone')}
checked
>
Emma Stone
</Checkbox>
...
<hr>
<Button>Vote</Button>
<Button>Cancel</Button>
<Button style={float: 'right'}>Clear</Button>
</Dropdown>

Reusability

What happens when we have some complex components we want to reuse in multiple places? Let’s call these Wrapper Components. We want these wrappers to not be monoliths, so we first put the building blocks together as above, and then we could use something like this:

<MultiselectDropdown
options={optionsObject}
onCheck={this.actions.doSomething()}
selectedOption={this.state.selectedOption}
/>

Conclusion

In many cases it’s acceptable to abstract the state of some components. E.g. when dealing with a Calendar component, you might not want to use props for every time the user clicks an Arrow (left or right) to select the date range. This is fine. However, this should be the exception, not the rule.

In other words, when you start writing your components, expose as many props as possible first, and then consider whether you want to hide some of them. Going the other way around will limit both the reusability of those components and their scalability. I would even argue that the Dropdown first presented in this article should never be allowed to go on production.