0
Help us understand the problem. What are the problem?

posted at

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

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

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

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オブジェクトの型を定義できるようにする

type ItemBase = {
    id: string
}
export const useCustomState = <T extends ItemBase>(defaultItems: T[] = []) => {}

typescript のジェネリックを使い、管理するオブジェクトの入力補完やコンパイルエラーが発生するようにします。
image.png
image.png

プロパティ "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 とすることで、指定キーがオブジェクト内に存在することを保証しつつ、入力補完を効かせます。
image.png

アップデートでは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: プロパティを必須から外す

image.png

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
0
Help us understand the problem. What are the problem?