🎁
「Bulletproof React」から学ぶ、FieldWrapperを使って統一レイアウトのフォームを実装する
先日Bulletproof Reactを読んでいたら便利なコンポーネントがあったので、自分なりの解釈を加えて実装してみました。
コード、デモが見たい方はこちらからどうぞ。
FieldWrapper
ってどんなコンポーネント??
Input
やSelect
などのフォームコンポーネントをラップするコンポーネントです。
FieldWrapper.tsx
import { FC, ReactNode } from 'react';
import { twMerge } from 'tailwind-merge';
type FieldWrapperProps = {
label?: string;
error?: string;
description?: string;
className?: string;
children: ReactNode;
};
export type FieldWrapperPassThroughProps = Omit<
FieldWrapperProps,
'children' | 'className'
>;
export const FieldWrapper: FC<FieldWrapperProps> = ({
label,
error,
className,
description,
children,
}) => {
return (
<div>
<label className={twMerge('block mb-1', className)}>
{label}
{children}
</label>
{description && <p className='text-gray-500'>{description}</p>}
{error && (
<div role='alert' className='text-red-500'>
{error}
</div>
)}
</div>
);
};
twMerge
や設計については、私が以前執筆したこちらの記事を参考にしてください。
使い方
InputField.tsx
import { forwardRef } from 'react';
import { FieldWrapper, FieldWrapperPassThroughProps } from '../FieldWrapper';
import { Input, InputProps } from './Input';
export type InputFieldProps = InputProps & FieldWrapperPassThroughProps;
export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(
({ label, error, description, ...props }, ref) => {
return (
<FieldWrapper label={label} error={error} description={description}>
<Input {...props} ref={ref} />
</FieldWrapper>
);
}
);
InputField.displayName = 'Input';
なんで便利なの?
先程のInputField
を使ってみましょう。
いい感じですね、では他のフォームコンポーネントを作成してみましょう。
InputField
と同じようにSelectField
を作成してみます。
SelectField.tsx
import { forwardRef } from 'react';
import { FieldWrapper, FieldWrapperPassThroughProps } from '../FieldWrapper';
import { Select, SelectProps } from './Select';
export type SelectFieldProps = SelectProps & FieldWrapperPassThroughProps;
export const SelectField = forwardRef<HTMLSelectElement, SelectFieldProps>(
({ label, error, description, ...props }, ref) => {
return (
<FieldWrapper label={label} error={error} description={description}>
<Select {...props} ref={ref} />
</FieldWrapper>
);
}
);
SelectField.displayName = 'Select';
ではSelectField
を使ってみましょう。
先程のInputField
と並べてみます。
レイアウトの責務をFieldWrapper
に任せることで、意図しないレイアウト崩れを防ぐことができます。
ここでは省略しますが、TextAreaField
やCheckboxField
なども同様に作成することができます。
まとめ
いかがでしたでしょうか。
FieldWrapper
が便利なコンポーネントであることが少しでも伝われば嬉しいです。
GitHub のリポジトリ、CodeSandbox を用意しましたので、興味がある方は是非ご覧ください。
Discussion