Controlled components using hooks
Adam Klein
Last updated on Dec 13, 2018

The repeating problem when creating a generic component

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

Currently, there are two approaches for implementing it, both of which have benefits and drawbacks:

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

or: yikes, going back to view-controller using hooks

We write a controlled component, but we also export a controller that works out of the box, using hooks.

Here is a 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,
    ...
  }
}

We separate the controlled component, which is just the view layer, from its state-manager, or ‘controller’ (and yes, I did it. I just used view-controller terminology :)) Then we export both, so that the user can enjoy both worlds.

Now, we can use the component and its state manager as is:

import { DataTable, useDataTableState } from ‘data-table’;

const MyComp = () => {
  const dataTableState = useDataTableState();
  return <DataTable {...dataTableState}  data={ … }/>;
}

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.

Here are some examples of using the controlled state:

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

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

Back to all articles

© 500Tech. Building high-quality software since 2012.