FormでのuseRefの使い方

useRefフックを使うと、DOMにアクセスしてその内容を取得・変更できます。他にもReactの公式ドキュメントでは、状態変数(props又はstate)の一つ前の状態を記憶するという使い方も紹介されています(How to get the previous props or state?)。よって用途はかなり広いのですが、本稿ではuseRefを使って、submitされたformの内容を参照し取得する方法をご紹介します。formではより一般的な、useStateを使う方法とその問題点についても、本稿の後段でご紹介します。

useRefを使用したsubmit

下記はuseRefを使用してsubmitされた内容を取得(参照)する事例です。form内にはlabel、input、buttonの3つの要素を配置し、buttonをクリックしてsubmitするとformにあるonSubmit関数が実行されるという仕組みです。以下、順を追ってuseRefを理解する上で重要な部分を中心に説明いたします。

FormSubmit.js

import { useRef } from "react";

function FormSubmit() {
const inputRef = useRef();
const onSubmit = (e) => {
e.preventDefault();
console.log(inputRef.current.value);
inputRef.current.value = "";
};

return (
<div>
<form onSubmit={onSubmit}>
<label htmlFor="name">Input here</label>
<input
type="text"
id="name"
ref={inputRef}
/>
<button type="submit">
Submit
</button>
</form>
</div>
);
}

export default FormSubmit;

1行でuseRefをインポート、関数コンポーネントのFormSubmit内、1行目でuseRefを使い変数を宣言・定義しています(ここではinputRef)。useRefの括弧内の引数では、初期値を定めることができますが、本事例はinputの内容を取得する目的でuseRefを使用するので、初期値を与えていません。

const inputRef = useRef();

次は、submit時に実行されるobSubmit関数です。引数はevent(e)を受け、1行目では再読み込みされないよう、e.preventDefault()としています。2行目でuseRefで取得したinputの内容をconsole.logしています(実際はconsole.logではなく、バックエンドにPOSTすることが多いと思います)。inpuRefは冒頭で定義したuseRefの変数(オブジェクト)で、inputRef.current.valueにて、現在ref属性にて参照されているDOMの内容を取得することができます。必要な処理(本件ではconsole.log)を終えた3行目では.current.valueを空にしています。冒頭で説明したuseRefを使ったDOMの内容の変更です。

const onSubmit = (e) => {
e.preventDefault();
console.log(inputRef.current.value);
inputRef.current.value = "";
};

本事例でref属性が設定されているDOMはinputで、ref={inputRef}が設置されています。よって、obSubmit実行時に、input内に記述されたテキストをuseRef.current.valueで取得することができます。また、obSubmitの3行目ではDOMのcurrent.valueの内容が空に変更されているので、submit実行時には必要な処理を終えた後にインプットに表示の内容が消えます。以上がsubmitされた内容を参照し取得するuseRefの使い方です。

<input
type="text"
id="name"
ref={inputRef}
/>

同じことをuseStateフック使って実行したのが次の事例です。

FormSubmit.js

import { useState } from "react";

function FormSubmit() {
const [textinput, setTextinput] = useState();
const onSubmit = (e) => {
e.preventDefault();
console.log(textinput);
setTextinput("")
};

return (
<div>
<form onSubmit={onSubmit}>
<label htmlFor="name">Input here</label>
<input
type="text"
id="name"
value={textinput}
onChange={(e) => setTextinput(e.target.value)}
/>
<button type="submit">
Submit
</button>
</form>
</div>
);
}

export default FormSubmit;

上記でも問題なく動きますが、問題点はinputに1文字1文字入力される都度、useStateの内容が更新される(Render)される点です。onChangeの部分が該当する処理です。useStateではuseRefのように一時点(入力終了後)のDOMの内容を参照することができませんので、このようにonChangeの都度、状態を更新する必要があります。20文字あれば20回Renderされますので非効率、速度の低下にも繋がります。自分のバックエンドへのPOSTであれば遅いだけで済みますが、外部のAPIにPOSTする際には、useStateを使った方法では文字数だけPOSTされ、先方にも負担、あるいは優良であれば高くつきます。その点refが付されたDOMがいざ参照されるまでは動かないuseRefは、useStateよりずっと効率化にformの最終的な内容だけを取得することができます。今日も最後まで読んで頂きありがとうございました。