Skip to content

React Hook Form で useEffect を使用してデフォルト値を設定する方法

問題の概要

React Hook Form を使用してユーザーデータの更新フォームを作成する際、非同期でデータを取得した後にフォームのデフォルト値を設定しようとすると、値が入力フィールドに反映されない問題があります。

元のコードでは、APIから取得したデータを useState で状態管理し、Controller コンポーネントの defaultValue プロパティに設定していますが、これは機能しません。

jsx
const [userData, setUserData] = useState(null);
const { handleSubmit, control } = useForm({ mode: 'onBlur' });

// データ取得関数
const fetchUserData = useCallback(async account => {
  const userData = await fetch(`${URL}/user/${account}`).then(res => res.json());
  setUserData(userData);
}, []);

// フォーム内での使用例
<Controller
  as={<input type='text' />}
  control={control}
  defaultValue={userData ? userData.name : ''}
  name='name'
/>

解決策

React Hook Form では、非同期で取得したデータをフォームに設定するためにいくつかの方法があります。各方法の特徴と使い方を説明します。

方法1: reset メソッドを使用する(推奨)

最も一般的で効果的な方法は、useForm から提供される reset メソッドを使用することです。

jsx
import { useForm, Controller } from 'react-hook-form';

const UpdateUserData = () => {
  const [userData, setUserData] = useState(null);
  const { handleSubmit, control, reset } = useForm({ mode: 'onBlur' });

  const fetchUserData = useCallback(async account => {
    const response = await fetch(`${URL}/user/${account}`);
    const userData = await response.json();
    setUserData(userData);
    // データ取得後にresetでフォーム値を設定
    reset(userData);
  }, [reset]);

  useEffect(() => {
    const account = localStorage.getItem('account');
    fetchUserData(account);
  }, [fetchUserData]);

  // フォームコンポーネント
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller
        as={<input type="text" />}
        control={control}
        name="name"
        // defaultValueは不要
      />
      {/* 他のフィールド */}
    </form>
  );
};

TIP

reset メソッドはフォームのすべての状態(値、検証、ダーティ状態など)をリセットします。部分的な更新だけで済む場合は setValue を使用します。

方法2: setValue で個別に値を設定する

個別のフィールドのみを更新したい場合には setValue を使用します。

jsx
const { handleSubmit, control, setValue } = useForm({ mode: 'onBlur' });

useEffect(() => {
  if (userData) {
    setValue('name', userData.name, {
      shouldValidate: true,
      shouldDirty: false,
      shouldTouch: false
    });
    setValue('phone', userData.phone, {
      shouldValidate: true,
      shouldDirty: false,
      shouldTouch: false
    });
  }
}, [userData, setValue]);

方法3: 親コンポーネントからデフォルト値を渡す

データ取得を親コンポーネントで行い、取得したデータをプロパティとしてフォームコンポーネントに渡す方法です。

jsx
// 親コンポーネント
const ParentComponent = () => {
  const [userData, setUserData] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      const account = localStorage.getItem('account');
      const response = await fetch(`${URL}/user/${account}`);
      const data = await response.json();
      setUserData(data);
    };
    
    fetchData();
  }, []);
  
  return userData ? <UserForm defaultValues={userData} /> : <LoadingSpinner />;
};

// フォームコンポーネント
const UserForm = ({ defaultValues }) => {
  const { handleSubmit, control } = useForm({
    mode: 'onBlur',
    defaultValues // 直接defaultValuesを設定
  });
  
  // フォームレンダリング
};

方法4: React Hook Form v7の非同期デフォルト値

v7以上では、defaultValues に関数を渡して非同期でデフォルト値を設定できます。

jsx
const {
  formState: { isLoading },
  handleSubmit,
  control
} = useForm({
  mode: 'onBlur',
  defaultValues: async () => {
    const account = localStorage.getItem('account');
    const response = await fetch(`${URL}/user/${account}`);
    return response.json();
  }
});

// フォームのローディング状態を表示
if (isLoading) {
  return <LoadingSpinner />;
}

方法5: values プロパティを使用する(v7以上)

v7では values プロパティを使用してリアクティブにフォーム値を更新できます。

jsx
const { handleSubmit, control } = useForm({
  mode: 'onBlur',
  values: userData // userDataが更新されるとフォームも更新される
});

ベストプラクティスと注意点

  1. データ取得とフォーム表示の分離

    jsx
    // 推奨: データ取得完了までフォームを表示しない
    const ParentComponent = () => {
      const [userData, setUserData] = useState(null);
      
      useEffect(() => {
        // データ取得処理
      }, []);
      
      return userData ? <UserForm defaultValues={userData} /> : <LoadingSpinner />;
    };
  2. Yupスキーマでのデフォルト値定義

    jsx
    import * as Yup from "yup";
    
    const formSchema = Yup.object().shape({
      name: Yup.string().default(""),
      phone: Yup.string().default("")
    });
    
    const { register, handleSubmit } = useForm({
      resolver: yupResolver(formSchema),
    });
  3. 無限ループの回避values プロパティを使用する場合、状態更新が連鎖して無限ループが発生する可能性があるため注意が必要です。

WARNING

defaultValue プロパティはコンポーネントのマウント時に一度しか評価されません。非同期でデータを取得する場合には、resetsetValue を使用する必要があります。

まとめ

React Hook Form で非同期のデフォルト値を設定する主な方法は以下の通りです:

  • reset メソッド: フォーム全体をリセットする場合に最適
  • setValue メソッド: 個別フィールドを更新する場合に有用
  • 親コンポーネントからのデータ渡し: 関心の分離に適する
  • 非同期 defaultValues: v7以上の簡潔な解決策
  • values プロパティ: リアクティブな更新が必要な場合

アプリケーションのアーキテクチャと使用しているReact Hook Formのバージョンに合わせて、最適な方法を選択してください。