React Hook Formでフォームに初期値を非同期で与える

浮き沈み来ん世はさてもいかにぞと feat. React Hook Form
2020.12.21

唐突ですがcontrolled componentsで更新系のフォームを扱う場合には下記のようなパターンで描写されることが多いです.

  1. 初期処理
  2. DOMの描写が走る
  3. useEffectなどでデータを非同期にフェッチする
  4. 値を状態にセットする
  5. 再描写が走る

さらに唐突ですが, React Hook Form (RHF) はuncontrolled componentsを採用しているためパフォーマンスが優れています.
RFHは値の取得や変更をRef経由で行います. そのため下記のようなコードの場合, TextFieldのラベルがshrinkしません.

<TextField
  label="california"
  id="california"
  name="california"
  inputRef={register}
/>

californiaが沈んだままですね. これはいけません.

california-form

InputLabelPropsをいじれば見栄えはよくなりますが, 常にラベルが浮いた状態になります. ですが沈まぬことがあるでしょうか.
前置きが長くなりましたが今回はこの問題の対処をうまくできたので執筆していきます.

environment

  • RHF v6
  • Material UI v4
  • React 16.3

concept

コンセプトはかなりシンプルでフォームの描写前に値のフェッチなどを終わらせます. なので流れは下記のようになります.

  1. 初期処理
  2. ロード画面を描写させる
  3. useEffectなどでデータを非同期的にフェッチする
  4. setValue() を利用して値を入れる
  5. ロード画面の描写を状態を変更してformを描写する
  6. 再描写が走る

この中でポイントは2つです.

  • formの描写をデータの取得が終わった後に行う
  • useForm Hooks利用時に shouldUnregister をfalseにする

shouldUnregister をfalseにすることでformがマウントされていない状態でも入力を維持することができます. これを利用してformの描写前に入力を更新する, つまり初期値を入れることができます.

Code

では実際のコードをみていきましょう. TypeScriptで書いていますが実際に動くやつではないので読み替えなりしてください.
Formのコンポーネントだけみていきましょう.

export const CaliforniaForm: React.FC = () => {
  const { register, handleSubmit, setValue } = useForm<CaliforniaFormInput>({
    // defaultがtrueなのでfalseに切り替える
    shouldUnregister: false,
  });
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const get = async () => {
      // 非同期的にデータをとってくる
      const california = await fetchCalifornia('awesome');

      // setValueでRHFのformに値を入れる
      setValue('california', california.data);

     // setterを呼び出すことで再レンダリングが走る
      setLoading(false);
    };
    get();
  }, [])

  if (loading) {
    return <CircularProgress />;
  }

  return (
    <form>
      <TextField
        label="california"
        id="california"
        name="california"
        inputRef={register}
      />
    </form>
  );
}

useEffectの中で, fetch → setValue → setLoadingの順で呼び出すことができます. これでデータを取得してからマウント前のformに対して初期値を保持して, フォームを最終的にレンダリングするといった実装ができます.

これでcaliforniaは浮き沈みできるようになりました. おめでとうございます.

in the end

Reactが何を契機に再描写が走るかや, 仮想DOMのコンセプトを知っていると動作や実装をどうすべきか考えやすいと感じました.
この記事がお役にたったら幸いです.

references