はじめに
こんにちは、watnowの滝村です。この記事は、watnow Advent Calendar 2025 の2日目の記事です。
今回は、僕がReactを勉強していて一番最初に躓いた、Hooksの代表格である 「useState」 について説明していきたいと思います。
そもそも useState とは
ざっくり言うと、コンポーネントに「状態(データ)」を持たせ、変更があったら画面を自動で書き換える(再レンダリングする)ための機能(Hooks) です。
ちなみにHooksとは、関数コンポーネントでReactの便利な機能(状態管理やライフサイクルなど)を使うための関数の総称です。
なぜ変数じゃダメなの?
let などの普通の変数だと、変数の値が変わってもReactはそれに気づけません。そのため、内部でデータが変わっても画面上の数字はずっと古いまま……ということが起きてしまいます。 「データが変わったら、画面も更新してほしい!」という時に useState が必要になります。
基本的な使い方
まずは書き方を見てみましょう。
import { useState } from "react";
// const [今の値, 更新するための関数] = useState(初期値);
const [count, setCount] = useState(0);
このように、分割代入を使って受け取ります。
-
第1引数
count: 現在の値(読み取り専用)。 -
第2引数
setCount: 値を更新するための関数。これを使うと再レンダリングが走ります。 慣例としてset○○と書かれることが多いです。 -
初期値
0: 最初に画面に表示される値。
実践:カウントアップボタン
ボタンを押すと数字が増えるシンプルなコンポーネントを作ってみます。
"use client"
import { useState } from "react";
export default function CountUp() {
const [count, setCount] = useState(1);
const handleClick = () => {
// 現在の count に 1 を足した値で更新する
setCount(count + 1);
};
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={handleClick}>クリック</button>
</div>
);
}
これでボタンを押せば数字が増えていきます。しかし、実はこの書き方setCount(count + 1)には落とし穴があります。
更新関数の罠と解決策
よくある間違い
「ボタンを1回押したら、一気に3増えるようにしたい」と思って、以下のように書いたとします。
const [count, setCount] = useState(1);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
handleClick が実行されると、countの中身は 1+3 で 4 になると思いますよね? しかし実際は、countの中身は 2 にしかなりません。
なぜ起きるのか?
これは、handleClick 関数が実行された瞬間の count の値が 「1」 だからです。Reactへの命令は以下のようになります。
- 1つ目の
setCount(1 + 1)が予約される - 2つ目の
setCount(1 + 1)が予約される(まだ画面上のcountは 1 のまま!) - 3つ目の
setCount(1 + 1)が予約される
結果として、Reactは「あ、全部 2 にしたいのね」と判断して、最終的に「2」にしかなりません。
解決策:関数型アップデート
これを防ぐために、更新関数には「新しい値」を渡すのではなく、「直前の値を受け取って計算する関数」を渡します。これを関数型アップデートと呼びます。
// 良い書き方
const handleClick = () => {
setCount((prevCount) => prevCount + 1); // 1 -> 2
setCount((prevCount) => prevCount + 1); // 2 -> 3
setCount((prevCount) => prevCount + 1); // 3 -> 4
};
こうすることで、常に最新の状態(直前の計算結果) を受け取って処理できるため、正しく 4 になります。
「ボタン連打」などの激しい操作や、複雑なアプリになった時にバグを防ぐためにも、「今の値を使って新しい値を計算する」ときは、この関数型アップデートを使うのが鉄則です。
その他の活用例
最後に、カウンター以外のよくある使い方を紹介します。
テキスト入力フォーム
入力された文字をそのまま保存する場合は、値を直接渡すパターンでOKです。
"use client"
import { useState } from "react";
export default function TextInput() {
const [name, setName] = useState("");
const handleName = (e) => {
// 入力欄の値をそのままセットする
setName(e.target.value);
};
return (
<div>
<input type="text" onChange={handleName} value={name} />
<p>入力された名前: {name}</p>
</div>
);
}
表示/非表示を切り替える(トグル)
「表示されているなら非表示に」「非表示なら表示に」という処理は、現在の状態true/false に依存するので、関数型アップデートを使います。
"use client"
import { useState } from "react";
export default function Toggle() {
const [open, setOpen] = useState(false);
const toggle = () => {
// 今の状態(open)を受け取り、その逆(!open)を返す
setOpen((prevOpen) => !prevOpen);
}
return (
<div>
<button onClick={toggle}>
{open ? "隠す" : "表示する"}
</button>
{open && <p>ここに秘密のメッセージが表示されます!</p>}
</div>
);
}
おわりに
今回はReact開発の基本である useState について解説しました。
- 値が変わると画面も変わる(再レンダリング)
- 更新するときは「値を渡す」か「関数を渡す」かの2パターンある
- 今の値を使って計算するときは「関数を渡す」ほうが安全
このあたりを覚えておけば、基本はばっちりです! 他にもReactには useEffect など様々なHooksが存在します。まずは useState を使いこなして、色々なUIを作ってみてください。
最後まで読んでいただきありがとうございました!
参考