カスタムHooks useState でオブジェクト配列を扱う場合のテンプレート的なものです。
typescriptも便利な機能を十分に活用し簡潔で機能的な記述を行います。
オブジェクト配列を扱う場合には、1つのアイテムを追加したり、削除の必要が多いかと思います。
そのような機能を簡潔に実装し、汎用的に使える想定のカスタムステートです。
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オブジェクトの型を定義できるようにする
type ItemBase = {
id: string
}
export const useCustomState = <T extends ItemBase>(defaultItems: T[] = []) => {}
typescript のジェネリックを使い、管理するオブジェクトの入力補完やコンパイルエラーが発生するようにします。
プロパティ "id" は必須にする
T extends ItemBase
とすることで、利用するオブジェクトにはIDを必須にします。これにより各メソッドの処理を保証します。
#簡単なフィルターを実装する
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
とすることで、指定キーがオブジェクト内に存在することを保証しつつ、入力補完を効かせます。
#アップデートではidの上書きを防ぐ
// 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: プロパティを必須から外す
これで、typescriptの機能を有効に使い、スッキリとオブジェクト配列管理のuseStateを書くことができました。