🎃

【Typescript】【React-Hook-Form】のビルドインコンポーネントを利用した自作コンテキストの統合

2023/01/26に公開

概要

React-Hook-Formでフォームを作成しているとビルドインの関数以外のカスタムメソッドも同梱して、ContextAPIから配信したい時がありましたので、実装してみました。

役に立ちそうな場面

  • ReactのContextAPIを使ってもいいが、わざわざContextを作成して、配信するほどでもない
  • 状態管理は、サードパーティ製の状態管理ライブラリを使用していない
  • useFormで出力されるメソッドと共にフォーム作成時に依存する自作の関数やデータも共に配信したい

まずは、React-Hook-Formの中身から確認

src/useFormContext.tsx
~省略~
export const useFormContext = <
  TFieldValues extends FieldValues,
>(): UseFormReturn<TFieldValues> =>
  React.useContext(HookFormContext) as unknown as UseFormReturn<TFieldValues>;
  
~省略~
export const FormProvider = <TFieldValues extends FieldValues, TContext = any>(
  props: FormProviderProps<TFieldValues, TContext>,
) => {
  const { children, ...data } = props;
  return (
    <HookFormContext.Provider value={data as unknown as UseFormReturn}>
      {children}
    </HookFormContext.Provider>
  );
};

上記のライブラリのソースを見てみると ContextApiをラッパーさせていることがわかります。
なので、 FormProviderコンポーネントの引数に、自作のデータも一緒に入れてしまえばそのまま、データも共に配信してくれることがわかりました。

ソース

sample.tsx
type TFormCustomValues = {
    hoge: string
    huga: number
}

type UseFormCustomReturn<T extends TFormCustomValues, U = any> = UseFormReturn<T, U> & {
    state: [boolean, Dispatch<SetStateAction<boolean>>];
    func: () => void;
};


const useFormCustom = <T extends TFormCustomValues, U = any>(
    props: UseFormProps<TFormCustomValues, U>,
): UseFormCustomReturn<T, U> => {
    const form = useForm<TFormCustomValues>(props);
    const { setValue } = form;
    const state = useState(false);

    const func = useCallback(
        () => {
            setValue('huga', 1);
        },
        [],
    );

    return {
        ...(form as unknown as UseFormCustomReturn<T, U>),
        state,
        func,
    };
};

const useFormCustomContext = <
    T extends TFormCustomValues,
    U = any,
>(): UseFormCustomReturn<T, U> => useFormContext<T>() as any;

const SomeComponent = () => {
    const form = useFormCustom({
        defaultValues: {
            hoge: 'hoge',
            huga: 1,
        }
    })
    
    return (
        <FormProvider {...form}>
            <ChildComponent/>
        </FormProvider>
    )
}

const ChildComponent = () => {
    const { state , func } = useFormCustomContext<TFormCustomValues>()
    console.log('state', state) // [boolean, Dispatch<SetStateAction<boolean>>]
    console.log('func', func) // () => void
    return <div></div>
}

解説

type UseFormCustomReturn<T extends TFormCustomValues, U = any> = UseFormReturn<T, U> & {
    state: [boolean, Dispatch<SetStateAction<boolean>>];
    func: () => void;
};

上記で、「useForm」(React-Hook-Formのカスタムフック)で返される返り値の型をオーバーライドします。

const useFormCustom = <T extends TFormCustomValues, U = any>(
    props: UseFormProps<TFormCustomValues, U>,
): UseFormCustomReturn<T, U> => {
    const form = useForm<TFormCustomValues>(props);
    const { setValue } = form;
    const state = useState(false);

    const func = useCallback(
        () => {
            setValue('huga', 1);
        },
        [state],
    );

    return {
        ...(form as unknown as UseFormCustomReturn<T, U>),
        state,
        func,
    };
};

const useFormCustomContext = <
    T extends TFormCustomValues,
    U = any,
>(): UseFormCustomReturn<T, U> => useFormContext<T>() as any;

新たなカスタムフックを作成して、そこで「useForm」を呼び出します。
このカスタムフックの引数は、useFormで必要なプロパティをそのまま引き継ぎます。(他にも必要なプロパティがあれば別途追加できる)

UseFormCustomReturnの型で定義した自作のデータの実体を定義し、返却値に加えます。
useFormCustomContextの定義では、先ほど作成した「UseFormCustomReturn」の型を返り値の型として定義し、自作のデータが返却値に加わるように、既存の「useFormContext」関数をオーバーライドします。

const SomeComponent = () => {
    const form = useFormCustom({
        defaultValues: {
            hoge: 'hoge',
            huga: 1,
        }
    })
    
    return (
        <FormProvider {...form}>
            <ChildComponent/>
        </FormProvider>
    )
}

const ChildComponent = () => {
    const { state , func } = useFormCustomContext<TFormCustomValues>()
    console.log('state', state) // [boolean, Dispatch<SetStateAction<boolean>>]
    console.log('func', func) // () => void
    return <div></div>
}

「useFormCustomContext」でカスタムデータを呼び出すことができます。

Discussion