Skip to main content

第107章:練習:ref を受け取れるカスタムMyInput部品を作る

ref を受け取れるカスタム MyInput 部品を作ろう!


107-1 今日のゴール ✅

今日は、

親コンポーネント → MyInput<input>ref をバトンリレーして、 親から「フォーカスして〜」と命令できるようにする 🎤

という「Ref リレー」をやってみます 💪

React 19 では、もう forwardRef を書かなくても、 コンポーネントがふつうの Props と同じように ref を受け取れる ようになりました 🧡(Qiita)

なので今回は、その「新しい書き方」で MyInput コンポーネントを作っていきます。


107-2 ざっくり完成イメージ 🧠

最終的には、App.tsx からこんな風に使えるようにします。

  • AppinputRef を作る
  • MyInputref={inputRef} で渡す
  • ボタンを押すと、その ref を使ってフォーカス ✨

イメージとしてはこんな感じ 👇

実際のコードでは、

  • App.tsxuseRefinputRef を作る
  • MyInput.tsxref を含んだ Props の型を作り、<input> にそのまま渡す

という流れで実装します。


107-3 MyInput.tsx を作ろう 🛠

1️⃣ ファイルを作成

Vite のプロジェクトで、src/components/MyInput.tsx を作ります。

(フォルダ構成例)

  • src/

    • App.tsx

    • main.tsx

    • components/

      • MyInput.tsx ← いま作るやつ

2️⃣ Props の型を考える 🧩

React 19 では「ref もふつうの Props の一員」として扱えるようになりました。(React)

さらに、TypeScript 側では

ComponentPropsWithRef<"input">

というユーティリティ型を使うと、 <input> が本来持っている

  • value
  • onChange
  • placeholder
  • ref など…

まるごとセットで受け取る ことができます 🎁(Qiita)

そこに、MyInput 独自の label プロパティだけ足してあげる、というスタイルにします。


3️⃣ MyInput.tsx のコード ✍️

// src/components/MyInput.tsx
import type { ComponentPropsWithRef, FC } from "react";

// 🔸 <input> が本来受け取れる Props + ref をぜんぶ持っている型
type InputBaseProps = ComponentPropsWithRef<"input">;

// 🔸 MyInput 独自の props を足した型
type MyInputProps = InputBaseProps & {
label: string;
};

export const MyInput: FC<MyInputProps> = ({ label, ...inputProps }) => {
return (
<label style={{ display: "block", marginBottom: "12px" }}>
<span
style={{
display: "block",
fontSize: "14px",
marginBottom: "4px",
}}
>
{label}
</span>

{/* ⭐ ここがポイント:ref も含めて {...inputProps} でそのまま渡す */}
<input
{...inputProps}
style={{
padding: "8px 12px",
borderRadius: "4px",
border: "1px solid #ccc",
width: "100%",
boxSizing: "border-box",
}}
/>
</label>
);
};

📌 ポイント

  • ComponentPropsWithRef<"input">

    • <input> が受け取れる props(type, placeholder, onChange など)
    • ref
    • ぜんぶまとめた型 です。
  • { label, ...inputProps }

    • label だけ取り出して
    • ref を含む残り全部 (inputProps) を <input> に渡しています。
  • つまり App から <MyInput ref={inputRef} /> と書くと、 その ref は最終的に <input> までちゃんと届く、という仕組みです ✨


107-4 App.tsx から使ってみる 🎮

次は、App.tsx 側で MyInput を使ってみましょう。

1️⃣ useRefinputRef を作る

// src/App.tsx
import { useRef } from "react";
import { MyInput } from "./components/MyInput";

export default function App() {
// 🔸 HTMLInputElement を指す ref
const inputRef = useRef<HTMLInputElement | null>(null);

const handleFocusClick = () => {
// current が null じゃなければ focus する
inputRef.current?.focus();
};

return (
<div style={{ maxWidth: 400, margin: "40px auto", padding: "0 16px" }}>
<h1 style={{ fontSize: "20px", marginBottom: "16px" }}>
MyInput + ref 練習 💡
</h1>

<MyInput
label="お名前"
placeholder="ここに入力してね ✨"
ref={inputRef} // ← ここが大事!
/>

<button
type="button"
onClick={handleFocusClick}
style={{
padding: "8px 16px",
borderRadius: "4px",
border: "none",
backgroundColor: "#4f46e5",
color: "white",
cursor: "pointer",
}}
>
入力欄にフォーカスする 🔍
</button>
</div>
);
}

2️⃣ 実際の動きをチェック 👀

  1. npm run dev で開く

  2. 画面に

    • ラベル「お名前」
    • テキスト入力
    • 「入力欄にフォーカスする」ボタン が見えるはずです。
  3. ボタンをクリックすると、 👉 入力欄にカーソルがピッと当たるはずです ✨

うまく動いたら、 React 19 の「ref を prop として渡す」書き方ができた! ということです 🎉


107-5 「ref の流れ」をもう一回だけ整理 🧵

文字だけだとイメージしづらいので、 もう一度、流れをざっくり図にしておきます 🧠

  • App.tsx で作った inputRef を、
  • MyInput が「ただ受け取って、そのまま <input> に渡す」だけ、というシンプル構造です ✨

ここが理解できていれば OK です ✅


107-6 よくあるエラー&ハマりポイント 😵‍💫

🧨 1. Property 'ref' does not exist on type ...

原因候補

  • MyInputPropsref が含まれていない書き方をしている
  • 例:type MyInputProps = { label: string } だけ、など

対策

  • この章では必ず ComponentPropsWithRef<"input"> をベースにしましょう。
type InputBaseProps = ComponentPropsWithRef<"input">;
type MyInputProps = InputBaseProps & { label: string };

🧨 2. ref を <input> に渡し忘れる

MyInput 内で <input> をこう書いてしまうパターン:

<input placeholder="..." /> // ← ref が渡ってない!

これだと App.tsx から ref を渡しても、 最終的に <input> に届かない ので、focus() も効きません 🥲

✅ 正しいパターン(この章のやり方):

<input {...inputProps} />

inputProps の中に ref も入っているので、 まとめて渡してあげるイメージです。


🧨 3. React 19 じゃない環境で同じ書き方をしている

React 18 以前では、ref は特別扱いされていて、 コンポーネントの引数 { ref } に素直には入ってきませんでした。

React 19 では 新しい JSX トランスフォームが必須で、 これが有効になっていると ref を Props として扱えるようになります。(React)

Vite + 最新の React テンプレートなら、 ふつうはもう新しいトランスフォームになっているので、 この章のコードでそのまま動くはずです 💡

もし古いプロジェクトを React 19 に上げた場合は、 公式の Upgrade Guide に沿って JSX トランスフォームを 更新しておくと安心です。(React)


107-7 ミニ練習問題 📝(やれたら超えらい)

時間と気力があれば、次の 2 つにチャレンジしてみてください 💪

✅ 練習1:エラーメッセージ付き MyInput

MyInput に、こんな Props を追加してみましょう。

  • errorMessage?: string

仕様:

  • errorMessage が渡されているときだけ、

    • <input> の下に赤い文字でエラー文を表示する
    • <input> の枠線の色も赤くする

ヒント:

  • MyInputProps にプロパティを足すだけで OK
  • スタイルは style={{ ... }} で軽くやってしまって大丈夫 👍

✅ 練習2:ページ読み込み時に自動フォーカス

App.tsx を少し改造して、

  • 画面を開いた瞬間に inputRef.current?.focus() する
  • つまり「ページ読み込みで自動フォーカス」する

ようにしてみましょう。

ヒント:

  • すでに学んでいる useEffect を使う
  • 依存配列は [](マウント時に1回だけ)

107-8 今日のまとめ 🍵

  • React 19 では、コンポーネントがふつうに ref を Props として受け取れる ようになった 🎉(Qiita)

  • TypeScript では

    • ComponentPropsWithRef<"input"> を使うと、
    • <input> の Props + refまるごと再利用 できて超ラク ✨(Qiita)
  • MyInput は、

    • 親から ref を受け取る
    • <input {...inputProps} /> にそのまま渡すだけ
    • 親は useRefref={inputRef}inputRef.current?.focus() で操作できる

ここまでできていれば、 「ref as prop」× TypeScript の基本パターンはクリア です 🎊

次の章では、さらに一歩進んで useImperativeHandle で「ref から呼べるメソッドだけ公開する」 というテクニックに進んでいきます 💪✨