Bad
// フォームの入力値の変更やバリデーションを自作で書いている
function BadComponent() {
const [user, setUser] = useState({ name: '', age: 0, email: '' });
const [errors, setErrors] = useState([]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setUser({ ...user, [e.target.name]: e.target.value });
if (!e.target.value) {
setErrors([...errors, '入力してください']);
}
if (e.target.name === 'age' && e.target.value < 0) {
setErrors([...errors, '0以上で入力してください']);
}
if (e.target.name === 'email' && !e.target.value.includes('@')) {
setErrors([...errors, 'メールアドレスを入力してください']);
}
}
const handleSubmit = (e: React.FormEvent) => {
if (errors.length > 0) return;
// 送信処理
axios.post('/api/users', user);
}
// フォームの表示
return {...};
}
Good
// zodを用いたバリデーション
const schema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
});
function GoodComponent() {
// React Hook Formを用いてフォームの入力値の変更やエラーメッセージの管理を行う
const { register, handleSubmit, formState: { errors } } = useForm<UserFormInput>(
resolver: zodResolver(schema),
);
const onSubmit = (data) => {
// 送信処理
axios.post('/api/users', data);
}
// フォームの表示
return {...};
}
Bad
function BadComponent() {
const items = [
{
icon: <SignInIcon />,
name: 'ログイン',
description: 'すでにアカウントをお持ちの方',
},
{
icon: <SignUpIcon />,
name: '新規登録',
description: 'アカウントをお持ちでない方',
},
]
return (
<div>
<Dropdown title="メニュー" items={items} hideIcons={false} />
</div>
)
}
function Dropdown({ title, items, hideIcons }) {
return (
<div>
<div>{title}</div>
{items.map((item) => (
<div>
{hideIcons ? null : item.icon}
<div>{item.name}</div>
<div>{item.description}</div>
</div>
))}
</div>
)
}
Good
function GoodComponent() {
;<Dropdown.Dropdown>
<Dropdown.Button>メニュー</Dropdown.Button>
<Dropdown.List>
<Dropdown.Item
icon={<SignInIcon />}
description="すでにアカウントをお持ちの方"
>
ログイン
</Dropdown.Item>
<Dropdown.Item
icon={<SignUpIcon />}
description="アカウントをお持ちでない方"
>
新規登録
</Dropdown.Item>
{/* 以下のようにすぐカスタマイズできる */}
<span className="px-1 text-xs leading-5 text-gray-400">
パスワードを忘れた方はこちら
</span>
</Dropdown.List>
</Dropdown.Dropdown>
}
Bad
// それぞれのダイアログがプライバシーポリシーに同意する処理を持っているため、考慮して改修する必要がある
function PrivacyDialog() {
const [isOpen, setIsOpen] = useState(false);
const handleAccept = (id: string) => {
// プライバシーポリシーに同意する処理
}
const openDialog = ("xx") => {
handleAccept();
setIsOpen(true);
}
const closeDialog = () => {
setIsOpen(false);
}
return (
<div>
<button onClick={openDialog}>ダイアログを開く</button>
<Dialog isOpen={isOpen} onClose={closeDialog}>
<div>ダイアログの内容</div>
</Dialog>
</div>
);
}
function PrivacyJPDialog() {
const [isOpen, setIsOpen] = useState(false);
const handleAccept = (country: string, id: string) => {
// 国別のプライバシーポリシーに同意する処理
}
const openDialog = () => {
handleAccept("ja", "xxx");
setIsOpen(true);
}
const closeDialog = () => {
setIsOpen(false);
}
return (
<div>
<button onClick={openDialog}>ダイアログを開く</button>
<Dialog isOpen={isOpen} onClose={closeDialog}>
<div>ダイアログの内容</div>
</Dialog>
</div>
);
}
Good
// どちらのダイアログも同じpropsを受け取るように型定義しているため、一方のダイアログに置き換えやすい
interface PrivacyDialogProps {
handleAccept: (id: string) => void;
}
function PrivacyDialog({ handleAccept }: PrivacyDialogProps) {
const [isOpen, setIsOpen] = useState(false);
const openDialog = () => {
handleAccept("xx");
setIsOpen(true);
}
...
}
function PrivacyJPDialog({ handleAccept }: PrivacyDialogProps) {
const [isOpen, setIsOpen] = useState(false);
const openDialog = () => {
handleAccept("xxx");
setIsOpen(true);
}
...
}
function PrivacyPage() {
return (
<>
<PrivacyDialog handleAccept={handleAccept} /> ←こっちは使わなくなる
<PrivacyJPDialog handleAccept={handleAccept} /> ←こっちを使う
</>
)
}
Bad
// productとuserをpropsとして渡しているため、片方が存在する場合にもう片方のpropsは不要になる
function BadNotification({ product, user }) {
if (product) {
return <div>{product.name}</div>
} else if (user) {
return <div>{user.name}</div>
} else {
return null
}
}
Good
// product, userそれぞれの通知コンポーネントを作成することで、不要なpropsを渡さないようにする
function GoodNotification() {
const { product } = useFetchProduct()
const { user } = useFetchUser()
return (
<>
{product && <ProductNotification product={product} />}
{user && <UserNotification user={user} />}
</>
)
}
function ProductNotification({ product }) {
return <div>{product.name}</div>
}
function UserNotification({ user }) {
return <div>{user.name}</div>
}
Bad
// Formコンポーネント内でAPIを叩く処理を持っている
function Form() {
...
const handleSubmit = async (e) => {
try {
e.preventDefault();
const data = {
// データ整形
};
await axios.post("api/v1/submit", data);
} catch (err) {
// エラーハンドリング
}
};
...
}
Good
// Formコンポーネント内でAPIを叩く処理を持たず、インスタンス化してpropsとして渡す
function Form({ submitService }) {
...
const handleSubmit = async () => {
await submitService.submitData(formData);
}
...
}
function FormPage() {
const submitServiceV1 = new SubmitService(
endpoints.SUBMIT.v1
);
const submitServiceV2 = new SubmitService(
endpoints.SUBMIT.v2
);
return <Form submitService={submitServiceV1} />;
}
export class SubmitService {
constructor(private submitEndpoints) {}
async submitFeedback(submitData) {
try {
const data = {
// データ整形
};
await axios.post(this.submitEndpoints.SUBMIT, data);
} catch (err) {
// エラーハンドリング
}
}
}