React でボタンを押すと数字が増える、入力欄の文字がリアルタイムに表示される——こうした「画面が変化する」しくみの中心にあるのが useState フックです。コンポーネントに状態(state)を持たせ、その値が変わると画面を自動で描き直してくれます。この記事では、useState の基本の書き方から、更新のしくみ、前の値をもとに更新する方法、オブジェクトや配列を扱うときの注意点まで、初心者がつまずきやすいポイントを押さえながら解説します。
目次
useState とは何か
useState は、関数コンポーネントに「状態」を持たせるための React のフック(機能)です。ここでいう状態とは、ボタンが押された回数や、フォームに入力された文字のように、時間とともに変化してその都度画面に反映したい値のことです。
ただの変数ではなく useState を使う理由は、React が「値が変わったこと」を検知して画面を再描画(再レンダリング)するためです。通常のローカル変数を書き換えても、React はそれに気づかず画面は更新されません。useState を通して値を変えることで、初めて表示が変わります。
基本の使い方
useState は、初期値を引数に渡して呼び出します。戻り値は「現在の値」と「更新用の関数」の2つが入った配列で、分割代入で受け取るのが定番です。ボタンを押すたびに数が増えるカウンターで見てみましょう。
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+値の名前(先頭大文字)」のペアにします。この命名に従うと、コードを読んだだけで何の状態かが分かりやすくなります。
const [name, setName] = useState(''); // 文字列
const [isOpen, setIsOpen] = useState(false); // 真偽値
const [items, setItems] = useState([]); // 配列
const [user, setUser] = useState(null); // 未取得を表す null
初期値の型が、そのままその状態の型になります。文字列なら ''、真偽値なら false、リストなら空配列 [] というように、扱いたいデータに合わせて初期値を決めます。
前の値をもとに更新する
更新関数には、値そのものだけでなく関数を渡すこともできます。この関数は「1つ前の状態」を引数で受け取り、新しい状態を返します。これを関数型の更新と呼びます。とくに、同じ更新を続けて呼ぶときに重要です。
// ❌ これだと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(レンダリング時点の値)を参照しているためです。たとえば count が 0 なら、3回とも 0 + 1 を計算するので結果は 1 にしかなりません。一方、関数型の更新なら React が「直前の最新の値」を prev に渡してくれるため、正しく 3 増えます。前の状態をもとに計算するときは、関数型の更新を使うと覚えておくと安全です。
オブジェクトや配列の状態を更新する
状態がオブジェクトや配列のときは、少し注意が必要です。React は状態が「別のものに変わったか」を、参照(同じオブジェクトかどうか)で判断します。そのため、既存のオブジェクトの中身を直接書き換えても、React は変化に気づきません。新しいオブジェクト・配列を作って渡すのが基本です。
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 で状態を更新すると、入力とともに表示が変わります。
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 の「動く画面」を自在に作れるようになります。