Обработка форм в ReactJS и Typescript с помощью пользовательских хуков

Сегодня я покажу вам простой способ обработки отправки форм в 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>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Это все, что нам нужно для создания пользовательского хука для обработки отправки формы, и он отлично работает. Вы можете посмотреть демонстрацию в реальном времени в следующей песочнице.

Подведение итогов

Если вам понравилась эта статья, то не забудьте нажать ❤️ и добавить ее в закладки для дальнейшего использования. Если у вас есть какие-либо вопросы или предложения, не стесняйтесь, пишите их. До встречи.

Оцените статью
Procodings.ru
Добавить комментарий