Part 5 · フォーム
制御コンポーネント
input の値を React が持つということ
Part 3 の「リストと key」で、こう書きました。
「入力欄に打った文字は、React が管理している値ではありません。 ブラウザの入力欄そのものが持っている値です」。
この章では、それを React の管理下に置きます。 そうすると入力に対してできることが一気に増えます。
React が値を知らない状態
入力欄をただ置くと、値を持っているのはブラウザです。 React は何が入力されたか知りません。
打てるが、React 側からは中身が分からない
文字は打てます。ですが React は値を知らないので、次のようなことが何ひとつできません。
- 入力が空のときは送信ボタンを押せなくする
- 文字数を表示する
- ボタンを押して中身を空にする
- 入力に応じて他の場所の表示を変える
Part 1 の言い方をすれば、入力欄の中身が状態になっていないということです。 状態でないものからは、何も決められません。
uncontrolled.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export function Uncontrolled() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
return (
<div className="flex flex-col gap-3">
{/* 値を React に渡していない。入力欄が自分で値を持っている */}
<Input placeholder="名前を入力" />
<div className="flex items-center gap-2">
<Button size="sm" disabled>
送信(今の値が分からないので何もできない)
</Button>
</div>
</div>
);
}値を React に持たせる
やることは 2 つだけです。
<Input
value={name} // 表示する値は state
onChange={(e) => setName(e.target.value)} // 入力があれば state を更新
/>value… 画面に出す値を、React が持っている state にするonChange… 打たれたら、その値で state を更新する
この形を制御コンポーネントと呼びます。 入力欄の見た目を React が制御している、という意味です。
文字数も、ボタンの状態も、クリアもできる
値が state になった途端、Part 4 でやったことがすべて使えるようになりました。 文字数は name.length で計算できますし、 送信できるかどうかも name.trim() !== "" で求まります。どちらも state にする必要はありません。
controlled.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useState } from "react";
export function Controlled() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [name, setName] = useState("");
// 入力が空かどうかは、state から計算できる
const canSubmit = name.trim() !== "";
return (
<div className="flex flex-col gap-3">
<Input
placeholder="名前を入力"
// 表示する値は React が持っているものにする
value={name}
// 入力があったら、React 側の値を更新する
onChange={(event) => setName(event.target.value)}
/>
<div className="flex flex-wrap items-center gap-2">
<Button size="sm" disabled={!canSubmit}>
送信
</Button>
<Button size="sm" variant="outline" onClick={() => setName("")}>
クリア
</Button>
<span className="text-muted-foreground">{name.length} 文字</span>
</div>
</div>
);
}一周まわって表示されている
この仕組みは、慣れるまで奇妙に見えます。 打った文字がそのまま出ているのではなく、一周まわってから表示されているからです。
キーを打つ
→ onChange が呼ばれる
→ setName で state が変わる
→ コンポーネントが実行し直される
→ value={name} に新しい値が入る
→ 画面に文字が出る遠回りに見えますが、これがあるおかげで入力を途中で加工できます。
// 数字以外は受け付けない
onChange={(e) => setTel(e.target.value.replace(/[^0-9]/g, ""))}
// 常に大文字にする
onChange={(e) => setCode(e.target.value.toUpperCase())}画面に出る値は最後まで React が決めているので、打った内容と違うものを表示することもできるわけです。
controlled.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useState } from "react";
export function Controlled() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [name, setName] = useState("");
// 入力が空かどうかは、state から計算できる
const canSubmit = name.trim() !== "";
return (
<div className="flex flex-col gap-3">
<Input
placeholder="名前を入力"
// 表示する値は React が持っているものにする
value={name}
// 入力があったら、React 側の値を更新する
onChange={(event) => setName(event.target.value)}
/>
<div className="flex flex-wrap items-center gap-2">
<Button size="sm" disabled={!canSubmit}>
送信
</Button>
<Button size="sm" variant="outline" onClick={() => setName("")}>
クリア
</Button>
<span className="text-muted-foreground">{name.length} 文字</span>
</div>
</div>
);
}他の入力欄でも同じ
// チェックボックスは checked
<input
type="checkbox"
checked={isAgreed}
onChange={(e) => setIsAgreed(e.target.checked)}
/>
// 選択肢も value
<select value={role} onChange={(e) => setRole(e.target.value)}>
<option value="admin">管理者</option>
</select>チェックボックスだけ value ではなく checked を使います。 受け取る側も e.target.checked になります。 それ以外の考え方は同じです。
controlled.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useState } from "react";
export function Controlled() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [name, setName] = useState("");
// 入力が空かどうかは、state から計算できる
const canSubmit = name.trim() !== "";
return (
<div className="flex flex-col gap-3">
<Input
placeholder="名前を入力"
// 表示する値は React が持っているものにする
value={name}
// 入力があったら、React 側の値を更新する
onChange={(event) => setName(event.target.value)}
/>
<div className="flex flex-wrap items-center gap-2">
<Button size="sm" disabled={!canSubmit}>
送信
</Button>
<Button size="sm" variant="outline" onClick={() => setName("")}>
クリア
</Button>
<span className="text-muted-foreground">{name.length} 文字</span>
</div>
</div>
);
}理解できたか確かめる
確認クイズ
制御コンポーネントにすると何ができるようになる?
確認クイズ
入力欄に文字を打っても何も表示されない。原因は?
controlled.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useState } from "react";
export function Controlled() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [name, setName] = useState("");
// 入力が空かどうかは、state から計算できる
const canSubmit = name.trim() !== "";
return (
<div className="flex flex-col gap-3">
<Input
placeholder="名前を入力"
// 表示する値は React が持っているものにする
value={name}
// 入力があったら、React 側の値を更新する
onChange={(event) => setName(event.target.value)}
/>
<div className="flex flex-wrap items-center gap-2">
<Button size="sm" disabled={!canSubmit}>
送信
</Button>
<Button size="sm" variant="outline" onClick={() => setName("")}>
クリア
</Button>
<span className="text-muted-foreground">{name.length} 文字</span>
</div>
</div>
);
}この章のまとめ
- 入力欄をただ置くと、値を持つのはブラウザ。React は中身を知らない
valueとonChangeをつなぐと、 値が state になる(制御コンポーネント)- state になれば、文字数も送信可否も計算で求められる
- 打った文字は一周まわって表示される。だから途中で加工できる
valueだけ渡してonChangeを忘れると、打てない入力欄になる
controlled.tsx
"use client";
import { useTrackDemoRender } from "@/components/lesson/demo-card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useState } from "react";
export function Controlled() {
// このカードが描き直された回数を数えるための 1 行(教材の仕掛け)
useTrackDemoRender();
const [name, setName] = useState("");
// 入力が空かどうかは、state から計算できる
const canSubmit = name.trim() !== "";
return (
<div className="flex flex-col gap-3">
<Input
placeholder="名前を入力"
// 表示する値は React が持っているものにする
value={name}
// 入力があったら、React 側の値を更新する
onChange={(event) => setName(event.target.value)}
/>
<div className="flex flex-wrap items-center gap-2">
<Button size="sm" disabled={!canSubmit}>
送信
</Button>
<Button size="sm" variant="outline" onClick={() => setName("")}>
クリア
</Button>
<span className="text-muted-foreground">{name.length} 文字</span>
</div>
</div>
);
}