3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

カスタム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を書くことができました。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?