Write better components using hooks

Let’s take an example of a table component that allows actions like selection, sorting, etc.:

Table Component

There are two approaches to implementing it:

Controlled — the benefits are full control over the component’s state and the ability to intercept actions and state. The downside is a lot of boilerplate to use the component as is.
Uncontrolled — the benefit is that it just works without added boilerplate. But the problem is that we have no knowledge or control over its internal state.

The Solution — component + controller (with hooks!)

We separate the controlled component, which is just the view layer, from its state-manager, or ‘controller’ (yikes, I just used view-controller terminology :))

Here is the component, which is completely controlled, and gets everything using props:

export const DataTable = ({ data, selection, sorting, setSelection, setSorting, ... })

And the logic is separated to a custom hook:

export const useDataTableState = (...) => {
const [selection, setSelection] = useState(initialSelection);
const [sorting, setSorting] = useState(initialSorting);
...
}

We return it as an object, so we can spread it directly to the component’s props:

return {
selection,
setSelection,
sorting,
setSorting,
...
}

And now, we can use the component and its state manager as is, and enjoy the best of all worlds:

import { DataTable, useDataTableState } from ‘data-table’;
const MyComp = () => {
const dataTableState = useDataTableState();
return <DataTable {...dataTableState} data={ … }/>;
}

The useDataTableState function does all the wiring for us.

The added-value is that we are in control of the data table state, and we can use it, or decide to intercept some of its values/actions.

Like this example:

import { DataTable, useDataTableState } from ‘data-table’;
const MyComp = () => {
const dataTableState = useDataTableState();
return (<div>
<DataTable {...dataTableState} data={ … }/>
<span>Selected { size(dataTableState.selection) } rows</span>
<button onClick={ dataTableState.setSelection({}) }>Clear selection</button>
</div>);
}

Here is the complete pseudo-implementation:

export const DataTable = ({ data, selection, sorting, setSelection, setSorting, ... }) => (
// this is the controlled component implementation
...
)
export const useDataTableState = (initialSelection = {}, initialSorting = null, ...) => {
// here we implement the state management logic
const [selection, setSelection] = useState(initialSelection);
const [sorting, setSorting] = useState(initialSorting);
...
return {
selection,
setSelection,
sorting,
setSorting,
...
}
}

This approach could be beneficial for both open source writers, and for writing reusable components in your company.