💨

Reactで行うインタラクティビティの追加

2023/10/17に公開

今回はインタラクションを処理し、時間の経過とともに異なる出力を表示するコンポーネントの作成方法について学んでいきます。

イベント応答

React では、JSX にイベントハンドラを追加することができます。イベントハンドラはあなた自身で書く関数であり、クリック、ホバー、フォーム入力へのフォーカスといったユーザインタラクションに応答してトリガされます。

下記のコードはReactを使用して複雑なイベントハンドリングと状態管理を行っています。この例は、クリックに応じてカウンターの増減とアラート表示を組み合わせたものです。

export default function App() {
  const [count, setCount] = useState(0);
  const [showAlert, setShowAlert] = useState(false);

  const handleIncrement = () => {
    setCount(count + 1);

    if (count + 1 === 5) {
      setShowAlert(true);
    } else {
      setShowAlert(false);
    }
  };

  const handleDecrement = () => {
    setCount(count - 1);

    if (count - 1 === 0) {
      setShowAlert(true);
    } else {
      setShowAlert(false);
    }
  };

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
      {showAlert && <div>Alert: The counter is at its limit!</div>}
    </div>
  );
}
  1. useState フックを使用して、count(カウンターの値)と showAlert(アラートを表示するかどうかのフラグ)の状態を追跡しています。

  2. handleIncrement 関数は「Increment」ボタンがクリックされたときに呼び出され、カウンターの値を増加させ、特定の条件をチェックしてアラートを表示するようにしています。

  3. handleDecrement 関数は「Decrement」ボタンがクリックされたときに呼び出され、カウンターの値を減少させ、同様にアラートを表示するようにしています。

  4. アラートを表示するために、{showAlert && <div>Alert: The counter is at its limit!</div>} を使用しています。showAlert が true の場合にのみアラートが表示されます。

state:コンポーネントのメモリ

useStateフックを使用すると、コンポーネントに state を追加することができます。フックとはコンポーネントに React の機能を使用させるための特別な関数です(state はその機能の 1 つです)。useState フックを使うと state 変数を宣言できます。このフックは初期値 (initial state) を受け取り、現在の state と、それを更新するための state セッタ関数のペアを返します。

useStateフックを使用して、コンポーネントの状態を管理する方法を体験してみましょう。以下の例では、カート内のアイテムを管理するシンプルなショッピングカートアプリを作成しています。

export default function ShoppingCartApp() {
  const [cartItems, setCartItems] = useState([]);
  const [totalPrice, setTotalPrice] = useState(0);

  const products = [
    { id: 1, name: 'Product A', price: 10 },
    { id: 2, name: 'Product B', price: 20 },
    { id: 3, name: 'Product C', price: 30 },
  ];

  const addToCart = (product) => {
    const updatedCartItems = [...cartItems, product];
    setCartItems(updatedCartItems);
    setTotalPrice(totalPrice + product.price);
  };

  const removeFromCart = (product) => {
    const updatedCartItems = cartItems.filter((item) => item.id !== product.id);
    setCartItems(updatedCartItems);
    setTotalPrice(totalPrice - product.price);
  };

  return (
    <div>
      <h1>Shopping Cart</h1>
      <div>
        <h2>Products</h2>
        <ul>
          {products.map((product) => (
            <li key={product.id}>
              {product.name} - ${product.price}
              <button onClick={() => addToCart(product)}>Add to Cart</button>
            </li>
          ))}
        </ul>
      </div>
      <div>
        <h2>Cart</h2>
        <ul>
          {cartItems.map((cartItem) => (
            <li key={cartItem.id}>
              {cartItem.name} - ${cartItem.price}
              <button onClick={() => removeFromCart(cartItem)}>Remove from Cart</button>
            </li>
          ))}
        </ul>
        <p>Total Price: ${totalPrice}</p>
      </div>
    </div>
  );
}
  1. cartItems ステートと totalPrice ステートを使用して、カート内のアイテムと合計価格を管理しています。

  2. addToCart 関数と removeFromCart 関数は、カート内のアイテムを追加および削除し、それに応じてステートを更新します。

  3. 商品リストとカート内のアイテムリストを表示し、各アイテムの横には「Add to Cart」ボタン(カートに追加)と「Remove from Cart」ボタン(カートから削除)があります。

  4. 合計価格が表示され、カート内のアイテムを追加または削除するたびに更新されます。

stateはスナップショット

JavaScript の変数とは異なり、React の state はスナップショットのような動作をします。セットしても既存の state 変数は変更されず、代わりに再レンダーがトリガされます

以下の例では、メッセージのスレッドを持つ簡単なチャットアプリを想定します。ユーザーがメッセージを送信すると、スレッド内にそのメッセージが表示され、選択した受信者にメッセージが送信されるシナリオです。

export default function ChatApp() {
  const [selectedThread, setSelectedThread] = useState('general');
  const [message, setMessage] = useState('');
  const [threads, setThreads] = useState({
    general: [],
    alice: [],
    bob: [],
  });

  const handleThreadChange = (e) => {
    setSelectedThread(e.target.value);
  };

  const handleMessageChange = (e) => {
    setMessage(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const updatedThreads = { ...threads };
    updatedThreads[selectedThread].push({ user: 'You', text: message });

    if (selectedThread !== 'general') {
      const replyUser = selectedThread === 'alice' ? 'Alice' : 'Bob';
      updatedThreads[selectedThread].push({ user: replyUser, text: `Reply to ${user}: ${message}` });
    }

    setThreads(updatedThreads);
    setMessage('');
  };

  const userOptions = ['general', 'alice', 'bob'];

  return (
    <div>
      <h1>Chat App</h1>
      <div>
        <label>
          Select Thread:{' '}
          <select value={selectedThread} onChange={handleThreadChange}>
            {userOptions.map((user) => (
              <option key={user} value={user}>
                {user}
              </option>
            ))}
          </select>
        </label>
      </div>
      <div>
        <h2>Thread: {selectedThread}</h2>
        <div>
          {threads[selectedThread].map((message, index) => (
            <div key={index}>
              <strong>{message.user}:</strong> {message.text}
            </div>
          ))}
        </div>
      </div>
      <form onSubmit={handleSubmit}>
        <textarea
          placeholder="Enter your message"
          value={message}
          onChange={handleMessageChange}
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}
  1. selectedThread ステートを使用して、選択したメッセージスレッド(受信者)を管理します。

  2. message ステートを使用して、入力されたメッセージを管理します。

  3. threads ステートは、各スレッドのメッセージ履歴を管理するためにオブジェクトとして使用します。

  4. handleThreadChange ハンドラは、スレッド(受信者)の選択を更新します。

  5. handleMessageChange ハンドラは、メッセージの入力を更新します。

  6. handleSubmit ハンドラは、メッセージの送信を処理し、スレッド内のメッセージ履歴を更新します。また、選択したスレッド以外にも応答メッセージを追加します。

一連の state の更新をキューに入れる

各ボタンが異なる値をスコアに追加します。通常のsetScore(score + value)のように、スコアに値を追加しようとすると問題が発生します。ボタンをクリックするとスコアが正しく更新されません

export default function AdvancedCounter() {
  const [score, setScore] = useState(0);

  function increment(value) {
    setScore(score + value);
  }

  return (
    <>
      <button onClick={() => increment(1)}>+1</button>
      <button onClick={() => increment(3)}>+3</button>
      <button onClick={() => increment(5)}>+5</button>
      <h1>Score: {score}</h1>
    </>
  );
}

修正方法は、setScore関数内で直接 score の値にアクセスせず、更新用関数を使うことです。

export default function AdvancedCounter() {
  const [score, setScore] = useState(0);

  function increment(value) {
    setScore((prevScore) => prevScore + value);
  }

  return (
    <>
      <button onClick={() => increment(1)}>+1</button>
      <button onClick={() => increment(3)}>+3</button>
      <button onClick={() => increment(5)}>+5</button>
      <h1>Score: {score}</h1>
    </>
  );
}

この修正により、各ボタンをクリックしてもスコアが正しく更新されます。各ボタンのincrement関数は、正確な値を取得し、それを現在のスコアに加えるためにsetScore内の更新用関数を使用します。

Discussion