1. ホーム
  2. React

【React】useState の使い方|状態管理の基本と更新の注意点を解説

Share

React でボタンを押すと数字が増える、入力欄の文字がリアルタイムに表示される——こうした「画面が変化する」しくみの中心にあるのが useState フックです。コンポーネントに状態(state)を持たせ、その値が変わると画面を自動で描き直してくれます。この記事では、useState の基本の書き方から、更新のしくみ、前の値をもとに更新する方法、オブジェクトや配列を扱うときの注意点まで、初心者がつまずきやすいポイントを押さえながら解説します。

useState とは何か

useState は、関数コンポーネントに「状態」を持たせるための React のフック(機能)です。ここでいう状態とは、ボタンが押された回数や、フォームに入力された文字のように、時間とともに変化してその都度画面に反映したい値のことです。

ただの変数ではなく useState を使う理由は、React が「値が変わったこと」を検知して画面を再描画(再レンダリング)するためです。通常のローカル変数を書き換えても、React はそれに気づかず画面は更新されません。useState を通して値を変えることで、初めて表示が変わります。

基本の使い方

useState は、初期値を引数に渡して呼び出します。戻り値は「現在の値」と「更新用の関数」の2つが入った配列で、分割代入で受け取るのが定番です。ボタンを押すたびに数が増えるカウンターで見てみましょう。

Counter.jsx
import { useState } from 'react';

function Counter() {
  // [現在の値, 更新用の関数] = useState(初期値)
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>現在のカウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        +1
      </button>
    </div>
  );
}

export default Counter;

useState(0) でカウントの初期値を 0 に設定しています。count が現在の値、setCount がその値を更新する関数です。ボタンをクリックすると setCount(count + 1) が呼ばれ、React が新しい値で再レンダリングし、画面の数字が1つ増えます。ここで大切なのは、値を変えるときは必ず更新関数(setCount)を使うという点です。count = count + 1 のように直接代入しても画面は変わりません。

更新関数の命名と分割代入

useState が返す配列の名前は自由に付けられますが、慣習として「値の名前」と「set+値の名前(先頭大文字)」のペアにします。この命名に従うと、コードを読んだだけで何の状態かが分かりやすくなります。

states.jsx
const [name, setName] = useState('');       // 文字列
const [isOpen, setIsOpen] = useState(false); // 真偽値
const [items, setItems] = useState([]);      // 配列
const [user, setUser] = useState(null);      // 未取得を表す null

初期値の型が、そのままその状態の型になります。文字列なら ''、真偽値なら false、リストなら空配列 [] というように、扱いたいデータに合わせて初期値を決めます。

前の値をもとに更新する

更新関数には、値そのものだけでなく関数を渡すこともできます。この関数は「1つ前の状態」を引数で受け取り、新しい状態を返します。これを関数型の更新と呼びます。とくに、同じ更新を続けて呼ぶときに重要です。

update.jsx
// ❌ これだと1回分しか増えない
function addThree() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}

// ✅ 前の値を受け取って更新すれば3増える
function addThreeSafely() {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
}

上の addThree が期待どおり動かないのは、3回とも同じ count(レンダリング時点の値)を参照しているためです。たとえば count0 なら、3回とも 0 + 1 を計算するので結果は 1 にしかなりません。一方、関数型の更新なら React が「直前の最新の値」を prev に渡してくれるため、正しく 3 増えます。前の状態をもとに計算するときは、関数型の更新を使うと覚えておくと安全です。

オブジェクトや配列の状態を更新する

状態がオブジェクトや配列のときは、少し注意が必要です。React は状態が「別のものに変わったか」を、参照(同じオブジェクトかどうか)で判断します。そのため、既存のオブジェクトの中身を直接書き換えても、React は変化に気づきません。新しいオブジェクト・配列を作って渡すのが基本です。

object-state.jsx
const [user, setUser] = useState({ name: 'Taro', age: 20 });

// ❌ 直接書き換えても再レンダリングされない
function badUpdate() {
  user.age = 21;
  setUser(user); // 同じオブジェクトなので変化とみなされない
}

// ✅ スプレッド構文で新しいオブジェクトを作る
function goodUpdate() {
  setUser(prev => ({ ...prev, age: 21 }));
}

// 配列に要素を追加するときも新しい配列を作る
const [items, setItems] = useState([]);
function addItem(value) {
  setItems(prev => [...prev, value]);
}

{ ...prev, age: 21 } は、prev の中身をすべてコピーしつつ age だけを上書きした新しいオブジェクトを作ります。配列も同様に [...prev, value] で新しい配列を作って追加します。「元を書き換えず、新しく作って渡す」——これがオブジェクトや配列の状態を扱うときの鉄則です。

入力欄と状態を連動させる

実際のアプリでよく使うのが、フォームの入力値を状態として管理するパターンです。入力欄の value を状態に結びつけ、onChange で状態を更新すると、入力とともに表示が変わります。

NameInput.jsx
import { useState } from 'react';

function NameInput() {
  const [name, setName] = useState('');

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <p>こんにちは、{name || 'ゲスト'}さん</p>
    </div>
  );
}

入力するたびに onChange が発火して setName が呼ばれ、name が更新されます。すると再レンダリングが起き、下の段落に入力した名前がすぐ反映されます。このように「入力を state で管理する」形は制御コンポーネントと呼ばれ、React でフォームを扱うときの基本になります。

まとめ

useState は、コンポーネントに状態を持たせ、値が変わったときに画面を再描画するためのフックです。const [値, set関数] = useState(初期値) の形で使い、値を変えるときは必ず更新関数を通します。連続して更新するときや前の値をもとに計算するときは、setCount(prev => prev + 1) のような関数型の更新を使うと安全です。また、オブジェクトや配列を更新するときは、元を直接書き換えず、スプレッド構文で新しいものを作って渡すのが鉄則です。この基本を押さえれば、React の「動く画面」を自在に作れるようになります。

参考ページ