zukucode
主にWEB関連の情報を技術メモとして発信しています。

React useStateで一覧の追加と削除を行う

Reactで一覧を表示するときは、配列をuseStateへ保存できます。

今回は簡単な買い物リストを作り、項目の追加と削除を行います。

配列のpushspliceで既存のstateを直接変更するのではなく、追加後・削除後の新しい配列を作ります。Reactはstateの参照が変わったことを更新の手掛かりにするためです。また、削除対象は配列の位置ではなく、項目の安定したIDで指定します。

配列をuseStateへ保存する

一覧の項目を表す型を定義し、初期値を指定します。

ShoppingList.tsx
import { useState } from 'react';

type ShoppingItem = {
  id: number;
  name: string;
};

export const ShoppingList = () => {
  const [items, setItems] = useState<ShoppingItem[]>([
    { id: 1, name: 'りんご' },
    { id: 2, name: '牛乳' },
  ]);

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

ShoppingItem[]は、ShoppingItemの配列という意味です。

mapで配列の各項目をli要素へ変換します。keyには一覧内で重複しない値を指定します。

入力した項目を追加する

入力欄の値もstateへ保存し、追加ボタンで新しい配列を作ります。

項目を追加する処理
const [newItemName, setNewItemName] = useState('');

const handleAdd = () => {
  const name = newItemName.trim();
  if (!name) return;

  const newItem: ShoppingItem = {
    id: Date.now(),
    name,
  };

  setItems((current) => [...current, newItem]);
  setNewItemName('');
};

...currentで現在の項目を新しい配列へコピーし、末尾へnewItemを追加します。

この例では説明を簡単にするため、現在時刻をIDにしています。データベースへ保存するアプリケーションでは、登録APIが返すIDを使用します。

入力欄とボタンは以下のようにします。

<input
  value={newItemName}
  onChange={(event) => setNewItemName(event.target.value)}
  placeholder="商品名"
/>
<button type="button" onClick={handleAdd}>
  追加
</button>

選択した項目を削除する

filterを使い、削除するID以外の項目で新しい配列を作ります。

項目を削除する処理
const handleDelete = (id: number) => {
  setItems((current) => current.filter((item) => item.id !== id));
};

一覧の各項目へ削除ボタンを追加します。

<ul>
  {items.map((item) => (
    <li key={item.id}>
      {item.name}
      <button type="button" onClick={() => handleDelete(item.id)}>
        削除
      </button>
    </li>
  ))}
</ul>

ボタンが押された項目のidhandleDeleteへ渡します。

完成したコンポーネント

追加と削除をまとめると以下のようになります。

ShoppingList.tsx
import { useState, type FormEvent } from 'react';

type ShoppingItem = {
  id: number;
  name: string;
};

export const ShoppingList = () => {
  const [items, setItems] = useState<ShoppingItem[]>([]);
  const [newItemName, setNewItemName] = useState('');

  const handleAdd = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const name = newItemName.trim();
    if (!name) return;

    setItems((current) => [
      ...current,
      { id: Date.now(), name },
    ]);
    setNewItemName('');
  };

  const handleDelete = (id: number) => {
    setItems((current) => current.filter((item) => item.id !== id));
  };

  return (
    <section>
      <h1>買い物リスト</h1>
      <form onSubmit={handleAdd}>
        <input
          value={newItemName}
          onChange={(event) => setNewItemName(event.target.value)}
          placeholder="商品名"
        />
        <button type="submit">追加</button>
      </form>

      {items.length === 0 ? (
        <p>項目はありません。</p>
      ) : (
        <ul>
          {items.map((item) => (
            <li key={item.id}>
              {item.name}
              <button
                type="button"
                onClick={() => handleDelete(item.id)}
              >
                削除
              </button>
            </li>
          ))}
        </ul>
      )}
    </section>
  );
};

元の配列を直接変更しない

以下のようにpushしただけでは、Reactが変更を検出できない場合があります。

避ける例
items.push(newItem);

追加にはスプレッド構文、削除にはfilterを使い、新しい配列をsetItemsへ渡します。

useStateの値はメモリ上にあります。ページを再読み込みすると初期値へ戻ります。

値を残す場合は、APIを使ってデータベースへ保存する方法や、用途に応じてlocalStorageへ保存する方法を使用します。

配列のstateを更新するときは、現在の配列を直接変更せず、新しい配列を作ることがポイントです。


関連記事