# Refactor checkbox filters with React Custom Hook

On one of my projects, I had multiple sets of checkboxes, used for filtering vacation offers. There're filters for departure cities, destination places, board options, etc. Let's look together, at how we can achieve this using React 🙂

Let's assume we store the state for each filter set as an array. So when a checkbox is checked, its value is added to the array. It looks like this:

```javascript
board = []

// checking "All Inclusive" and "Breakfast"...

board = ['allInvlusive', 'breakfast']

// unchecking "Breakfast"

board = ['allInclusive']

# etc.
```

Additionally, we wanted to push the array with the state after each change to Redux, using `onChange` callback. I hope it's quite clear, isn't it? Let's go to code and 1st iteration!

## Step 1 - useState & useEffect per component

Let's start simple and implement storing state using `useState` hook and calling `onChange` callback under `useEffect`. `selectedBoard` is **the array** I mentioned earlier. It contains board options (IDs). Updating the state is performed by `setSelectedBoard` with the jsxhelper function `toggleItem`.

```typescript
const [selectedBoard, setSelectedBoard] = useState<string[]>([])

useEffect(
  () => {
    onChange(selectedBoard)
  }, [selectedBoard, onChange]
)
```

```xml
<input
  type="checkbox"
  name={ board.name }
  value={ board.id }
  checked={ isItemChecked(selectedBoard, board.id) }
  onChange={ e => toggleItem(setSelectedBoard, e.target.value) }
/>
```

Nothing fancy. It just works 🙂 The problem is that we'll have unnecessary code repetition in the future: with this approach, we have to copy `useState` and `useEffect` block for each filter component...

Fortunately, we can easily fix this, by extracting the hook logic to a more generic **custom hook**. What we'll do in the 2nd step.

## Step 2 - Extract hook-related logic to a Custom Hook

The second step is all about extracting hook-related code to a separate file, wrapping it under `useCheckboxes` function (naming is up to you, by convention it starts with `use`) and return an object with functions related to the state.

Actually, `useCheckboxes` could return anything, like the array, instead of the object. In this case, it makes sense to use an object, since I expect `useChecbkoxes` to be growing, so it's much easier to import its functionalities via object keys.

### useCheckboxes - Custom React Hook

As you can see, it uses the same `useState` and `useEffect` logic with the generic name `Items`.

```typescript
export const useCheckboxes = ({ onChange, defaultItems }: Props) => {
  const [selectedItems, setSelectedItems] = useState<string[]>(defaultItems)

  const isItemChecked = (itemId: string): boolean => {
    return selectedItems.includes(itemId)
  }

  const toggleItem = (itemId: string): void => {
    setSelectedItems((prevState: string[]) => {
      if (prevState.includes(itemId)) {
        return prevState.filter(item => item !== itemId)
      }
      else {
        return [...prevState, itemId]
      }
    })

  useEffect(
    () => {
      onChange(selectedItems)
    }, [selectedItems, onChange]
  )

  return { isItemChecked, toggleItem }
```

### Using the useCheckboxes custom hook

To use `useCheckboxes` for the Board component, we just need to import the functions and make some minor updates.

```typescript
const { isItemChecked, toggleItem } = useCheckboxes({ onChange, defaultItems: [] })

...

<input
  type="checkbox"
  name={ board.name }
  value={ board.id }
  checked={ isItemChecked(board.id) }
  onChange={ e => toggleItem(e.target.value) }
/>
```

And that's it! Now we have a generic `useCheckboxes` which can be easily used anywhere, without code repetition! Now, the code is much easier to maintain.

Also, it brings us a great bonus - now the checkbox logic can be easily tested.

### useCheckboxes tests

```javascript
import { renderHook, act } from '@testing-library/react-hooks'
import { useCheckboxes } from '../useCheckboxes'

const onChange = jest.fn()

it('#isItemCheck returns correct value', () => {
  const { result } = renderHook(() => useCheckboxes({ onChange, defaultItems: ['XYZ'] }))

  expect(result.current.isItemChecked('XYZ')).toBeTruthy()
  expect(result.current.isItemChecked('ABC')).toBeFalsy()
})

it('#toggleItem toggles the given value and calls onChange', () => {
  const { result } = renderHook(() => useCheckboxes({ onChange, defaultItems: ['XYZ'] }))

  expect(onChange).toHaveBeenCalledTimes(1)
  expect(result.current.isItemChecked('XYZ')).toBeTruthy()
  expect(result.current.isItemChecked('ABC')).toBeFalsy()

  act(() => result.current.toggleItem('ABC'))

  expect(onChange).toHaveBeenCalledTimes(2)
  expect(result.current.isItemChecked('ABC')).toBeTruthy()
  expect(result.current.isItemChecked('XYZ')).toBeTruthy()
})
```

That's pretty nice! But, that's not the end. Let's suppose that we need to add some more features, like clearing an individual/all items (also without calling `onChange` in some cases), pulling state from Redux, etc.

Handling state became more complex, so we can do one more refactor - use React `useReducer`. And we'll do it!

## Step 3 - useState + useEffect = useReducer

`useReducer` hook manages state very similarly to `Redux`. It dispatches actions, which are handled by the reducer, which returns a new state.

The biggest advantage of this approach is we have a finite set of actions and we can get rid of `useEffect`, which by its asynchronous nature sometimes leads to unpredictable behavior (especially when managing a complex state).

### Refactor to useCheckboxesReducer

As I mentioned, the reducer is a Redux-like function taking `state` and `action` as arguments. For clarity, I listed just part of the code.

```typescript
export enum ActionType {
  TOGGLE_ITEM = 'TOGGLE_ITEM',
  CLEAR_ITEM = 'CLEAR_ITEM',
  ...
}

export const useCheckboxesReducer = (state: string[], action: useCheckboxesReducerAction) => {
  switch (action.type) {
    case ActionType.TOGGLE_ITEM: {
      const { itemId } = action
      const newState = state.includes(itemId) ? state.filter(item => item !== itemId) : [...state, itemId]
      action.onChange(newState)

      return newState
    }
    case ActionType.CLEAR_ITEM: {
      const newState = state.filter(item => item !== action.itemId)
      action.onChange(newState)

      return newState
    }
    
    ...
```

BTW, I like using `enum` for declaring action types. It's easy to export/import etc. Bear in mind, it's a TypeScript feature.

### Upadated useCheckboxes with useReducer

Now writing separate reducers pays off - `useChecboxes` becomes a very lean function 🚀. Its main goal is dispatching actions with demanded payload.

```typescript
export const useCheckboxes = ({ onChange, defaultItems }: Props): HookReturn => {
  const [checkedItems, dispatch] = useReducer(useCheckboxesReducer, defaultItems)

  const isItemChecked = (itemId: string): boolean => checkedItems.includes(itemId)
  const toggleItem = (itemId: string): void => dispatch({ type: ActionType.TOGGLE_ITEM, itemId, onChange })
  const clearItem = (itemId: string): void => dispatch({ type: ActionType.CLEAR_ITEM, itemId, onChange })
  ...

  return { isItemChecked, toggleItem, clearItem, ... }
}
```

## Wrap up

We went on a journey from simple state management with `useState` and `useEffect`, through extracting it to custom hook, to more complex code with `useReducer`.

We DRY-up the code and made it more maintainable and easy to test. Is this a good approach? I think so. But, only when you consider this as a path. Custom React Hook using `useReducer` definitely is not a silver bullet and you should end up with that solution, only when it makes sense. Wondering when? 🤔

My thoughts on this:

1. Managing a simple state, related to just one component? Basic `useState` and `useEffect` can make it.
    
2. The state logic is relatively simple, but you use this in many components? Extract it to a custom hook!
    
3. When managing the state is like bull riding, rewrite the code with `useReducer`.
    

Just be pragmatic 👍
