React Hook Form で useEffect を使用してデフォルト値を設定する方法
問題の概要
React Hook Form を使用してユーザーデータの更新フォームを作成する際、非同期でデータを取得した後にフォームのデフォルト値を設定しようとすると、値が入力フィールドに反映されない問題があります。
元のコードでは、APIから取得したデータを useState
で状態管理し、Controller
コンポーネントの defaultValue
プロパティに設定していますが、これは機能しません。
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
メソッドを使用することです。
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
を使用します。
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: 親コンポーネントからデフォルト値を渡す
データ取得を親コンポーネントで行い、取得したデータをプロパティとしてフォームコンポーネントに渡す方法です。
// 親コンポーネント
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
に関数を渡して非同期でデフォルト値を設定できます。
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
プロパティを使用してリアクティブにフォーム値を更新できます。
const { handleSubmit, control } = useForm({
mode: 'onBlur',
values: userData // userDataが更新されるとフォームも更新される
});
ベストプラクティスと注意点
データ取得とフォーム表示の分離
jsx// 推奨: データ取得完了までフォームを表示しない const ParentComponent = () => { const [userData, setUserData] = useState(null); useEffect(() => { // データ取得処理 }, []); return userData ? <UserForm defaultValues={userData} /> : <LoadingSpinner />; };
Yupスキーマでのデフォルト値定義
jsximport * as Yup from "yup"; const formSchema = Yup.object().shape({ name: Yup.string().default(""), phone: Yup.string().default("") }); const { register, handleSubmit } = useForm({ resolver: yupResolver(formSchema), });
無限ループの回避
values
プロパティを使用する場合、状態更新が連鎖して無限ループが発生する可能性があるため注意が必要です。
WARNING
defaultValue
プロパティはコンポーネントのマウント時に一度しか評価されません。非同期でデータを取得する場合には、reset
や setValue
を使用する必要があります。
まとめ
React Hook Form で非同期のデフォルト値を設定する主な方法は以下の通りです:
reset
メソッド: フォーム全体をリセットする場合に最適setValue
メソッド: 個別フィールドを更新する場合に有用- 親コンポーネントからのデータ渡し: 関心の分離に適する
- 非同期
defaultValues
: v7以上の簡潔な解決策 values
プロパティ: リアクティブな更新が必要な場合
アプリケーションのアーキテクチャと使用しているReact Hook Formのバージョンに合わせて、最適な方法を選択してください。