From c4a561c333f6f82678efcffef5ce3ed0f8e322f4 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 4 Nov 2023 17:14:25 +0100 Subject: refactor(components): rewrite ContactForm component * remove `Notice` props to handle it directly inside the form * replace `sendMail` prop with `onSubmit` prop * use `useForm` hook to handle fields --- .../organisms/forms/contact-form/contact-form.tsx | 342 ++++++++++----------- 1 file changed, 156 insertions(+), 186 deletions(-) (limited to 'src/components/organisms/forms/contact-form/contact-form.tsx') diff --git a/src/components/organisms/forms/contact-form/contact-form.tsx b/src/components/organisms/forms/contact-form/contact-form.tsx index ed23aad..8fea16d 100644 --- a/src/components/organisms/forms/contact-form/contact-form.tsx +++ b/src/components/organisms/forms/contact-form/contact-form.tsx @@ -1,16 +1,22 @@ -/* eslint-disable max-statements */ -import { - type ChangeEvent, - type FC, - type FormEvent, - type ReactNode, - useState, - useCallback, - useMemo, -} from 'react'; +import { type FC, useCallback } from 'react'; import { useIntl } from 'react-intl'; -import { useBoolean } from '../../../../utils/hooks'; -import { Button, Form, Input, Label, Spinner, TextArea } from '../../../atoms'; +import type { Nullable } from '../../../../types'; +import { + type FormSubmitHandler, + useForm, + type FormSubmitStatus, + type FormSubmitMessages, +} from '../../../../utils/hooks'; +import { + Button, + Form, + type FormProps, + Input, + Label, + Spinner, + TextArea, + Notice, +} from '../../../atoms'; import { LabelledField } from '../../../molecules'; import styles from './contact-form.module.scss'; @@ -21,19 +27,13 @@ export type ContactFormData = { object: string; }; -export type ContactFormProps = { - /** - * Set additional classnames to the form wrapper. - */ - className?: string; - /** - * Pass a component to print a success/error message. - */ - Notice?: ReactNode; +export type ContactFormSubmit = FormSubmitHandler; + +export type ContactFormProps = Omit & { /** - * A callback function to send mail. + * A callback function to handle form submit. */ - sendMail: (data: ContactFormData, reset: () => void) => Promise; + onSubmit?: ContactFormSubmit; }; /** @@ -43,92 +43,29 @@ export type ContactFormProps = { */ export const ContactForm: FC = ({ className = '', - Notice, - sendMail, + onSubmit, + ...props }) => { const formClass = `${styles.form} ${className}`; const intl = useIntl(); - const emptyForm: ContactFormData = useMemo(() => { - return { - email: '', - message: '', - name: '', - object: '', - }; - }, []); - const [data, setData] = useState(emptyForm); - const { - activate: activateNotice, - deactivate: deactivateNotice, - state: isSubmitting, - } = useBoolean(false); - - /** - * Reset all the form fields. - */ - const resetForm = useCallback(() => { - setData(emptyForm); - deactivateNotice(); - }, [deactivateNotice, emptyForm]); + const { messages, submit, submitStatus, update, values } = + useForm({ + initialValues: + /* The order matter: it will be reused to generate the fields in the right + * order. */ + { + name: '', + email: '', + object: '', + message: '', + }, + submitHandler: onSubmit, + }); - const updateForm = useCallback( - (e: ChangeEvent) => { - switch (e.target.name) { - case 'email': - setData((prevData) => { - return { ...prevData, email: e.target.value }; - }); - break; - case 'message': - setData((prevData) => { - return { ...prevData, message: e.target.value }; - }); - break; - case 'name': - setData((prevData) => { - return { ...prevData, name: e.target.value }; - }); - break; - case 'object': - setData((prevData) => { - return { ...prevData, object: e.target.value }; - }); - break; - default: - break; - } - }, - [] - ); - - const formName = intl.formatMessage({ - defaultMessage: 'Contact form', - description: 'ContactForm: form accessible name', - id: 'HFdzae', - }); - - const nameLabel = intl.formatMessage({ - defaultMessage: 'Name:', - description: 'ContactForm: name label', - id: '1dCuCx', - }); - - const emailLabel = intl.formatMessage({ - defaultMessage: 'Email:', - description: 'ContactForm: email label', - id: 'w4B5PA', - }); - - const objectLabel = intl.formatMessage({ - defaultMessage: 'Object:', - description: 'ContactForm: object label', - id: 's8/tyz', - }); - - const messageLabel = intl.formatMessage({ - defaultMessage: 'Message:', - description: 'ContactForm: message label', - id: 'yN5P+m', + const btnLabel = intl.formatMessage({ + defaultMessage: 'Send', + description: 'ContactForm: send button', + id: 'VkAnvv', }); const loadingMsg = intl.formatMessage({ @@ -137,92 +74,125 @@ export const ContactForm: FC = ({ id: 'xaqaYQ', }); - const submitHandler = useCallback( - async (e: FormEvent) => { - e.preventDefault(); - activateNotice(); - await sendMail(data, resetForm).then(() => deactivateNotice()); + const renderFields = useCallback(() => { + const entries = Object.entries(values) as [ + keyof ContactFormData, + ContactFormData[keyof ContactFormData], + ][]; + const labels = { + email: intl.formatMessage({ + defaultMessage: 'Email:', + description: 'ContactForm: email label', + id: 'w4B5PA', + }), + message: intl.formatMessage({ + defaultMessage: 'Message:', + description: 'ContactForm: message label', + id: 'yN5P+m', + }), + name: intl.formatMessage({ + defaultMessage: 'Name:', + description: 'ContactForm: name label', + id: '1dCuCx', + }), + object: intl.formatMessage({ + defaultMessage: 'Object:', + description: 'ContactForm: object label', + id: 's8/tyz', + }), + }; + + return entries.map(([field, value]) => { + const isRequired = field !== 'object'; + const inputType = field === 'email' ? 'email' : 'text'; + + return ( + + ) : ( + + ) + } + key={field} + label={ + + } + /> + ); + }); + }, [values, intl, update]); + + const renderNotice = useCallback( + ( + currentStatus: FormSubmitStatus, + msg: Nullable> + ) => { + switch (currentStatus) { + case 'FAILED': + return msg?.error ? ( + + {msg.error} + + ) : null; + case 'PENDING': + return ( + + {loadingMsg} + + ); + case 'SUCCEEDED': + return msg?.success ? ( + + {msg.success} + + ) : null; + default: + return null; + } }, - [activateNotice, data, deactivateNotice, resetForm, sendMail] + [loadingMsg] ); return ( -
- - } - label={ - - } - /> - - } - label={ - - } - /> - - } - label={} - /> - - } - label={ - - } - /> - - {isSubmitting ? {loadingMsg} : null} - {Notice} + {renderNotice(submitStatus, messages)} ); }; -- cgit v1.2.3