「Reactを勉強し始めたけど、stateって何?」
「なんでstateを使わないと画面が更新されないの?」
「useStateの使い方がいまいち分からない…」
Reactでアプリを作り始めると、必ず出会うのが「state(ステート)」という概念です。
実は、stateはReactアプリの心臓部。この仕組みを理解すると、ボタンをクリックしたら画面が変わる、フォームに入力したら表示が変わる、といった「動き」のあるアプリが作れるようになります。
この記事では、React Stateの基本から実践的な使い方まで、初心者の方にも分かりやすく解説していきますね!
React Stateとは?コンポーネントの「記憶」

Stateとは、Reactコンポーネントが持つ内部データのこと。日本語では「状態」と訳されます。
コンポーネントの「記憶」や「変数」のようなものだと考えてください。
コンポーネントって何?
コンポーネントとは、UIの部品のこと。ボタン、カード、フォームなど、再利用可能なパーツを指します。
実例:
function Counter() {
return <div>カウンター</div>;
}
これが最もシンプルなコンポーネントです。
なぜStateが必要なの?
普通の変数では、画面の更新が反映されないからです。
ダメな例(普通の変数):
function Counter() {
let count = 0;
const increment = () => {
count = count + 1; // 値は変わるけど...
console.log(count); // コンソールには表示される
};
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>増やす</button>
</div>
);
}
このコードでは、ボタンを押しても画面の数字は変わりません。コンソールには新しい値が表示されるのに、画面は0のままなんです。
理由:
Reactは、stateが変更されたときだけ画面を再描画(レンダリング)します。普通の変数が変わっても、Reactはそれに気づかないんです。
useState:関数コンポーネントでstateを使う
現代のReactでは、useStateというフック(Hook)を使ってstateを管理します。
useStateの基本
正しい例:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1); // これで画面も更新される!
};
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>増やす</button>
</div>
);
}
今度はボタンを押すと、画面の数字もちゃんと増えます!
useStateの構文
const [state, setState] = useState(初期値);
分解すると:
state:現在の状態の値setState:状態を更新するための関数useState(初期値):stateを作成するフック初期値:stateの最初の値
実例:
const [count, setCount] = useState(0);
// count は 0 からスタート
// setCount で count を更新できる
const [name, setName] = useState('太郎');
// name は '太郎' からスタート
const [isOpen, setIsOpen] = useState(false);
// isOpen は false からスタート
配列の分割代入
const [count, setCount]という書き方は、配列の分割代入というJavaScriptの機能です。
useStateは配列を返すので、こう書くことで2つの値を同時に取得できます。
別の書き方(こちらは冗長):
const countState = useState(0);
const count = countState[0];
const setCount = countState[1];
分割代入を使った方がスッキリしますね。
Stateの更新:画面を再描画させる
stateを更新するには、必ずsetterとして用意された関数を使います。
基本的な更新
function Counter() {
const [count, setCount] = useState(0);
// ✅ 正しい方法
const increment = () => {
setCount(count + 1);
};
// ❌ ダメな例
const badIncrement = () => {
count = count + 1; // これは動かない
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
関数型更新:前の値に基づいて更新
前の値を元に計算したい場合、関数を渡す方法が推奨されます。
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// 関数を渡す方法(推奨)
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}
なぜこちらが良いの?
複数回連続で更新する場合、正しく動作するからです。
比較:
// ❌ これだと1しか増えない
const badMultipleIncrement = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// すべて同じcountの値を参照するため
};
// ✅ これなら3増える
const goodMultipleIncrement = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 各更新が前回の結果を受け取る
};
様々な型のState
stateには、数値や文字列だけでなく、様々な型のデータを保存できます。
文字列のstate
function Greeting() {
const [name, setName] = useState('');
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力"
/>
<p>こんにちは、{name}さん!</p>
</div>
);
}
入力フィールドに文字を入力すると、リアルタイムで挨拶が表示されます。
真偽値(boolean)のstate
function Toggle() {
const [isOn, setIsOn] = useState(false);
return (
<div>
<button onClick={() => setIsOn(!isOn)}>
{isOn ? 'ON' : 'OFF'}
</button>
<p>スイッチは{isOn ? 'オン' : 'オフ'}です</p>
</div>
);
}
ボタンをクリックするたびに、ONとOFFが切り替わります。
オブジェクトのstate
function UserProfile() {
const [user, setUser] = useState({
name: '太郎',
age: 25,
email: 'taro@example.com'
});
const updateName = (newName) => {
setUser({
...user, // 既存のプロパティをコピー
name: newName // nameだけ更新
});
};
return (
<div>
<p>名前: {user.name}</p>
<p>年齢: {user.age}</p>
<button onClick={() => updateName('次郎')}>
名前を変更
</button>
</div>
);
}
重要:
オブジェクトを更新する際は、スプレッド構文(...)を使って既存の値をコピーする必要があります。
配列のstate
function TodoList() {
const [todos, setTodos] = useState(['買い物', '掃除']);
const addTodo = () => {
setTodos([...todos, '新しいタスク']);
};
const removeTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
return (
<div>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => removeTodo(index)}>削除</button>
</li>
))}
</ul>
<button onClick={addTodo}>追加</button>
</div>
);
}
複数のStateを管理する

一つのコンポーネントで、複数のstateを持つことができます。
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError('');
try {
// ログイン処理
await login(email, password);
} catch (err) {
setError('ログインに失敗しました');
} finally {
setIsLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button disabled={isLoading}>
{isLoading ? '送信中...' : 'ログイン'}
</button>
{error && <p style={{color: 'red'}}>{error}</p>}
</form>
);
}
関連するstateが多い場合は、一つのオブジェクトにまとめることもできます。
Stateのリフトアップ:親から子へ
複数のコンポーネントでstateを共有したい場合、共通の親コンポーネントにstateを置くという手法を使います。
これを「State のリフトアップ」と呼びます。
例:温度変換アプリ
function TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
// 摂氏から華氏に変換
const fahrenheit = (celsius * 9/5) + 32;
return (
<div>
<CelsiusInput
value={celsius}
onChange={setCelsius}
/>
<FahrenheitDisplay value={fahrenheit} />
</div>
);
}
function CelsiusInput({ value, onChange }) {
return (
<div>
<label>摂氏:</label>
<input
type="number"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
/>
</div>
);
}
function FahrenheitDisplay({ value }) {
return (
<div>
<p>華氏: {value.toFixed(1)}°F</p>
</div>
);
}
親コンポーネント(TemperatureConverter)がstateを管理し、子コンポーネントに値と更新関数を渡しています。
クラスコンポーネントのState(参考)
古いReactコードでは、クラスコンポーネントを使ったstate管理を見かけることがあります。
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
);
}
}
現代のReactでは関数コンポーネント+useStateが主流なので、新しいプロジェクトではクラスコンポーネントは使いません。
Stateを使う際の注意点とベストプラクティス
注意点1:Stateは直接変更しない
// ❌ ダメな例
const [user, setUser] = useState({ name: '太郎' });
user.name = '次郎'; // 直接変更してはいけない
// ✅ 正しい方法
setUser({ ...user, name: '次郎' });
注意点2:State更新は非同期
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
console.log(count); // まだ古い値が表示される!
};
setStateの直後にconsole.logしても、更新前の値が表示されます。更新は非同期で行われるためです。
注意点3:配列やオブジェクトは新しいものを作る
// ❌ ダメな例
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // 配列を直接変更
setItems(items); // 再レンダリングされないことがある
// ✅ 正しい方法
setItems([...items, 4]); // 新しい配列を作成
ベストプラクティス1:State は最小限に
計算で求められる値は、stateにしない方が良いです。
// ❌ 冗長な例
const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(0);
const increment = () => {
setCount(count + 1);
setDoubleCount((count + 1) * 2); // 無駄
};
// ✅ 良い例
const [count, setCount] = useState(0);
const doubleCount = count * 2; // 計算で求める
ベストプラクティス2:関連するStateはまとめる
// ❌ バラバラ
const [x, setX] = useState(0);
const [y, setY] = useState(0);
// ✅ まとめる
const [position, setPosition] = useState({ x: 0, y: 0 });
よくあるパターン:入力フォームの管理
実践的な例として、フォームの状態管理を見てみましょう。
シンプルなフォーム
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('送信データ:', formData);
// 送信処理
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
value={formData.name}
onChange={handleChange}
placeholder="名前"
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="メール"
/>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
placeholder="メッセージ"
/>
<button type="submit">送信</button>
</form>
);
}
一つのhandleChange関数で、すべての入力欄を管理できます。
useStateの代替:その他の状態管理
stateの管理方法は、useStateだけではありません。
useReducer
複雑な状態のロジックには、useReducerが適しています。
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
Context API
アプリ全体で共有したいstateには、Context APIが便利です。
外部ライブラリ
大規模アプリでは、以下のような状態管理ライブラリが使われます:
- Redux
- Zustand
- Recoil
- Jotai
よくある質問
Q1. useStateの初期値に関数を渡せる?
はい、できます。初期値の計算が重い場合に便利です。
// ✅ 関数を渡す(初回レンダリング時のみ実行)
const [state, setState] = useState(() => {
const initialValue = expensiveCalculation();
return initialValue;
});
Q2. 親コンポーネントから子のstateを変更できる?
基本的にはできません。親から子へはpropsで値を渡し、変更が必要な場合は親がstateを持つようにします(リフトアップ)。
Q3. stateが更新されても再レンダリングされない?
オブジェクトや配列を直接変更していませんか?新しいオブジェクト・配列を作成する必要があります。
Q4. 無限ループになってしまう
useEffectの依存配列にstateを入れて、その中でstateを更新していませんか?条件をつけて更新を制御しましょう。
まとめ:Stateはリアクティブなアプリの基礎
React Stateは、動的なWebアプリケーションを作るための核となる概念です。
React Stateの重要ポイント:
- コンポーネントが持つ内部データ
- stateが変わると画面が再レンダリングされる
- 関数コンポーネントではuseStateを使う
useStateの基本:
const [state, setState] = useState(初期値);
- state:現在の値
- setState:更新関数
- useState:Reactのフック
State更新のルール:
- 必ずsetter関数を使う
- 配列・オブジェクトは新しいものを作る
- 直接変更してはいけない
実践的なパターン:
- フォームの管理
- トグルスイッチ
- カウンター
- リスト操作
ベストプラクティス:
- stateは必要最小限に
- 計算で求められる値はstateにしない
- 関連するstateはまとめる
- 関数型更新を活用する
useStateをマスターすれば、ユーザーの操作に応じて変化する、リアクティブなアプリケーションが作れるようになります。
まずは簡単なカウンターから始めて、少しずつ複雑なstateの管理に挑戦してみてください。Reactの世界が広がりますよ!
楽しいReactライフを!


コメント