react hooks useState 実用的なオブジェクト配列管理

カスタムHooks useState でオブジェクト配列を扱う場合のテンプレート的なものです。
typescriptも便利な機能を十分に活用し簡潔で機能的な記述を行います。

オブジェクト配列を扱う場合には、1つのアイテムを追加したり、削除の必要が多いかと思います。
そのような機能を簡潔に実装し、汎用的に使える想定のカスタムステートです。


```react:useItems.ts
import { useState, useCallback } from 'react'

type ItemBase = {
    id: string
}

type FilterOptions = {
  not: boolean
}

export const useCustomState = <T extends ItemBase>(defaultItems: T[] = []) => {
  const [items, setItems] = useState<T[]>(defaultItems)

  // Filter read
  const filter = useCallback(
    (key: keyof T, value: any, options?: FilterOptions) => {
      if (options?.not) {
        return items.filter((item) => item[key] !== value)
      }

      return items.filter((item) => item[key] === value)
    },
    [items],
  )

  // Create
  const add = (item: T) => {
    setItems((prev) => [...prev, item])
  }

  // Update
  const update = (id: string, updateParams: Omit<Partial<T>, 'id'>) => {
    setItems((prev) => {
      const anotherItems = prev.filter((item) => item.id !== id)
      const currentItem = prev.find((item) => item.id === id)
      if (!currentItem) return prev

      return [
        ...anotherItems,
        {
          ...currentItem,
          ...updateParams,
        },
      ]
    })
  }

  // Remove
  const remove = (id: string) => {
    setItems((prev) => prev.filter((item) => item.id !== id))
  }

  return { items, filter, add, update, remove }
}
```

# Stateオブジェクトの型を定義できるようにする
```react
type ItemBase = {
    id: string
}
export const useCustomState = <T extends ItemBase>(defaultItems: T[] = []) => {}
```
typescript のジェネリックを使い、管理するオブジェクトの入力補完やコンパイルエラーが発生するようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203844/ff38d13a-8555-5680-ce6b-25d41a5ce063.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203844/ae1ea3e1-4216-3aa7-3917-74816e7cce50.png)

## プロパティ "id" は必須にする
`T extends ItemBase` とすることで、利用するオブジェクトにはIDを必須にします。これにより各メソッドの処理を保証します。

#簡単なフィルターを実装する
```react
type FilterOptions = {
  not?: boolean
}
  // Filter read
  const filter = useCallback(
    (key: keyof T, value: any, options?: FilterOptions) => {
      if (options?.not) {
        return items.filter((item) => item[key] !== value)
      }

      return items.filter((item) => item[key] === value)
    },
    [items],
  )
```
簡単なフィルタリングを用意。　`keyof T` とすることで、指定キーがオブジェクト内に存在することを保証しつつ、入力補完を効かせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203844/4925a79d-9ac2-fe3e-59c4-11760216768e.png)

#アップデートではidの上書きを防ぐ
```react
  // Update
  const update = (id: string, updateParams: Omit<Partial<T>, 'id'>) => {
    setItems((prev) => {
      const anotherItems = prev.filter((item) => item.id !== id)
      const currentItem = prev.find((item) => item.id === id)
      if (!currentItem) return prev

      return [
        ...anotherItems,
        {
          ...currentItem,
          ...updateParams,
        },
      ]
    })
  }
```

1つのアイテムのみを更新する場合は、スプレッド演算子によるマージを行います。
その最、 `Omit<Partial<T>, 'id'>`によって更新データには id を含めてはいけないようにすることで、idの上書きを防ぐことができます。

`Omit: 指定のプロパティを取り除く(今回は id を取り除く)`
`Partial: プロパティを必須から外す`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203844/a3ea4fd2-191c-b5c7-362a-245c54fc9784.png)


これで、typescriptの機能を有効に使い、スッキリとオブジェクト配列管理のuseStateを書くことができました。