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

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

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

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

配列のpushやspliceで既存の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>

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

完成したコンポーネント

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

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を更新するときは、現在の配列を直接変更せず、新しい配列を作ることがポイントです。


関連記事

  • TypeScript i18nextの動的な翻訳キーを抽出対象に含める

    i18nextでステータスに応じた文言を表示する場合、翻訳キーを動的に切り替えることがあります。ただし、実行時に正しく表示できることと、翻訳キーの抽出ツールが使用箇所を検出できることは別です。今回は、...


  • React useStateで入力フォームを作成する

    Reactでテキストボックスに入力した値を使用するには、useStateで値を保持します。今回は名前とメールアドレスを入力し、送信ボタンを押すと入力内容を表示するフォームを作成します。この例では入力値...


  • React useRefで入力欄にフォーカスを当てる

    入力フォームを開いた直後や、入力エラーが出たときに、特定の入力欄へフォーカスしたいことがあります。DOMの要素自体を参照するにはuseRefを使います。currentは初回描画中などにnullになり得...


  • React useMemoで重い計算結果を再利用する

    一覧の絞り込みや並べ替えを描画のたびに行うと、データ量や計算内容によっては画面操作が遅くなります。useMemoは、依存する値が変わらない間、以前の計算結果を再利用します。productsまたはque...


  • React useEffectで無限ループが発生するときに確認すること

    ReactのuseEffectを利用したときに無限ループが発生してしまうことがあります。特に注意したいのが、ESLintのreact-hooks/exhaustive-depsで表示された警告をUpd...


  • React useEffectの後片付けでイベント登録を解除する

    画面の幅が変わったときに表示を更新したい場合、ブラウザのresizeイベントを登録できます。登録したままにすると、コンポーネントが不要になったあとも処理が残るため、useEffectの後片付けを用意し...


  • React 関数コンポーネントにプロパティを設定する

    React+TypeScriptで作成した関数コンポーネントにプロパティを設定する方法を紹介します。以下は送信ボタンのコンポーネントを作成しています。isSubmittingというプロパティを用意して...


  • Reactで兄弟コンポーネントの状態を共有する

    検索欄に入力した文字列を、別の一覧コンポーネントでも使いたい場合があります。兄弟同士で別々にstateを持つと値がずれるため、共通の親にstateを置きます。入力欄は値を受け取り、変更を親へ通知します...


  • React useStateでselectの選択値を取得する

    Reactでプルダウンの選択値を扱うには、selectのvalueとonChangeを使用します。DOMから取得するvalueは、数値の選択肢を表示していても文字列です。選択値をそのまま識別子として扱...


  • React react-router-domの基本的な使い方

    React+TypeScriptのプロジェクトにreact-router-domを導入してSPAのアプリケーションを作成する方法を紹介します。まずはreact-router-domのパッケージをインス...