Сегодня я покажу вам простой способ обработки отправки форм в ReactJS & Typescript с помощью пользовательских хуков. Хуки – это многократно используемые функции. Когда у вас есть логика компонента, которая должна использоваться несколькими компонентами, мы можем извлечь эту логику в пользовательский хук.
Начало работы
Прежде всего, создайте файл use-form.ts
и экспортируйте функцию по умолчанию.
export default function useForm() {
}
Управление состоянием
В каждой форме мы должны работать с некоторыми данными, вводимыми пользователем. Затем мы обрабатываем данные и отправляем их на сервер. Для управления пользовательскими данными нам необходимо определить состояние. Итак, давайте определим состояние в нашем пользовательском хуке.
export default function useForm<T>(initialValue:T) {
const [formData, setFormData] = useState<T>(initialValue);
}
Здесь мы определяем состояние как formData
и для типизации принимаем общий T
в нашей функции useForm<T>
. Мы также принимаем initialData
в качестве первого аргумента нашего хука. Чтобы заполнить formData
извне хука, нам нужно вернуть его вместе с функцией-сеттером.
return {
data: formData,
setData(key: unknown, value?: unknown) {
if (typeof key === "string") {
setFormData((prev) => {
return {
...prev,
[key]: value
};
});
} else if (typeof key === "function") {
setFormData((prev: T) => key(prev));
} else {
setFormData(key as T);
}
}
};
В нашей функции setData
мы предоставляем три способа установить formData
.
- Передача
key
&value
. Мы можем передатьkey
иvalue
в качестве аргументов нашей функцииsetData
.
setData("name", "Something")
- Передача функции обратного вызова. Вы получите предыдущее состояние первым параметром вашей функции обратного вызова.
setData(prev => {...prev, [key]: "Updated Value"})
- Передача объекта. В этом случае весь наш
formData
будет заменен новым объектом.
setData({...newObject})
Отправка формы
Для отправки формы мы создадим функцию submit
внутри хука. Мы будем использовать axios для отправки formData
. В нашей функции submit
мы примем три аргумента.
- method: Метод HTTP (get, post, put, patch, delete и т.д.) для отправки запроса. Его тип будет
Method
(импортируйте его из axios). - url: URL вашего API, куда следует отправлять и передавать данные. Его тип будет
string
. - eventHandler: Здесь мы будем обрабатывать различные события (onSuccess, onError и т.д.), передавая функции обратного вызова. Мы обсудим это позже в этом блоге. Его тип будет
EventHandler
.
interface EventHandler {
onSuccess?: (res: unknown) => void;
onError?: (res: unknown) => void;
}
Вот как должен выглядеть наш метод submit
:
const submit = useCallback(
(method: Method, url: string, eventHandler: EventHandler = {}) => {
axios({
url,
method,
data: method === "get" ? {} : formData
})
.then((res) => console.log(res))
.catch((err) => console.log(err));
},
[formData]
);
Здесь мы оборачиваем нашу функцию хуком useCallback, потому что он запомнит нашу функцию submit. Теперь нам нужно вернуть некоторые вспомогательные функции, которые будут использовать нашу функцию submit
для отправки наших formData
.
return {
...,
get(url: string, eventHandler?: EventHandler) {
submit("get", url, eventHandler);
},
post(url: string, eventHandler?: EventHandler) {
submit("post", url, eventHandler);
},
put(url: string, eventHandler?: EventHandler) {
submit("put", url, eventHandler);
},
patch(url: string, eventHandler?: EventHandler) {
submit("patch", url, eventHandler);
},
destroy(url: string, eventHandler?: EventHandler) {
submit("delete", url, eventHandler);
}
}
Обработка ошибок
Здесь мы продемонстрируем только обработку ошибок валидации, когда код состояния 422
. Другие типы ошибок мы будем передавать в обработчик событий. Поэтому вы можете обрабатывать их по-своему. Итак, давайте определим состояние для ошибок
const [errors, setErrors] = useState<Record<string, unknown>>();
Теперь давайте обновим нашу функцию submit
следующим образом
const submit = useCallback(
(method: Method, url: string, eventHandler: EventHandler = {}) => {
setErrors({});
axios({
url,
method,
data: method === "get" ? {} : formData,
})
.then((res) => {
console.log(res)
})
.catch((err: any) => {
// This may be change based on your API;
if (err.response.status === 422) {
setErrors(err.response.data.errors);
}
console.log(err)
});
},
[formData]
);
Вернуть состояние errors
для доступа извне.
return {
...,
errors
}
Обработка состояния обработки
В типичном процессе отправки формы необходимо знать, что форма отправлена или обрабатывается. Исходя из этого, мы можем предотвратить отправку формы пользователем до получения какого-либо ответа.
Поэтому давайте определим состояние, в котором мы будем хранить состояние обработки формы.
const [processing, setProcessing] = useState(false);
Изначально это состояние будет иметь значение false. Когда мы вызовем метод submit, мы сделаем его true
. Затем, после получения любого ответа success
или error
, мы сделаем его false. Итак, вот как должен выглядеть наш метод submit
.
const submit = useCallback(
(method: Method, url: string, eventHandler: EventHandler = {}) => {
setProcessing(true);
setErrors({})
axios({
url,
method,
data: method === "get" ? {} : formData
})
.then((res) => {
setProcessing(false);
console.log(res);
})
.catch((err) => {
setProcessing(false);
// This may be change based on your API;
if (err?.response?.status === 422) {
setErrors(err.response.data.errors);
}
console.error(err);
});
},
[formData]
);
Теперь вернем состояние обработки, чтобы получить к нему доступ извне хука.
return {
...,
processing
}
Обработка событий
В нашей функции submit мы принимаем необязательный третий аргумент eventHandler
. Теперь мы проверим, подключен ли eventHandler
или нет, если какой-либо eventHandler
передан, мы вызовем его и передадим наш ответ
или ошибки
в eventHandler
.
const submit = useCallback(
(method: Method, url: string, eventHandler: EventHandler = {}) => {
setProcessing(true);
setErrors({})
axios({
url,
method,
data: method === "get" ? {} : formData
})
.then((res) => {
setProcessing(false);
eventHandler.onSuccess && eventHandler.onSuccess(res)
})
.catch((err) => {
setProcessing(false);
// This may change based on your API response
if (err?.response?.status === 422) {
setErrors(err.response.data.errors);
}
eventHandler.onError && eventHandler.onError(err)
});
},
[formData]
);
Как использовать
Наконец, наш пользовательский хук готов к обработке отправки формы. Давайте используем этот хук в нашем компоненте.
export default function App() {
const { data, setData, post, processing, errors } = useForm<User>({
firstName: "",
lastName: "",
email: ""
});
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
post("/data/v1/user/create", {
onSuccess: (res) => {
console.log(res);
toast.success("Registerd successfully");
},
onError: (err: any) => {
console.error(err.response);
toast.error("Something went wrong");
}
});
};
return (
<div className="App">
<form className="form" onSubmit={handleSubmit}>
<div>
<label htmlFor="first-name">First Name</label>
<br />
<input
onChange={(e) => setData("firstName", e.target.value)}
value={data.firstName}
type="text"
name="first-name"
id="first-name"
/>
{errors?.firstName && (
<p className="error">{errors.firstName as string}</p>
)}
</div>
<br />
...
<button disabled={processing} type="submit">
Submit
</button>
</form>
</div>
);
}
Это все, что нам нужно для создания пользовательского хука для обработки отправки формы, и он отлично работает. Вы можете посмотреть демонстрацию в реальном времени в следующей песочнице.
Подведение итогов
Если вам понравилась эта статья, то не забудьте нажать ❤️ и добавить ее в закладки для дальнейшего использования. Если у вас есть какие-либо вопросы или предложения, не стесняйтесь, пишите их. До встречи.