You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+TypeScript中onChange无值及event.preventDefault报错修复

问题描述

我在实现表单验证(useForm、yupResolver和Yup)之前,handleChange函数运行正常。现在console.log(value)无输出,输入框内容似乎没被记录,还抛出以下错误:

Uncaught (in promise) TypeError: event.preventDefault is not a function
at executeSearch (index.tsx:64:1)
at createFormControl.ts:1045:1

我是React和TypeScript新手,请问该怎么修复?

原代码

import React, { useState, useEffect, FormEventHandler } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import Loader from "react-ts-loaders";

import { WeatherApiVariables } from "../../../variables";
import { inputSchema } from "../../../validations/schema";

import lowTemperature from "../../../assets/icons/low-temperature-icon.svg";
import highTemperature from "../../../assets/icons/high-temperature-icon.svg";
import styles from "./weather-api.module.scss";

export function WeatherApi() {
  const [cityName, setCityName] = useState<string>("");
  const [cityWeather, setCityWeather]: any = useState<{}>({});
  const [cityWeatherForecast, setCityWeatherForecast]: any = useState<[]>([]);
  const [value, setValue] = useState("");
  const [loading, setLoading] = useState<boolean>(false);
  const {
    register,
    handleSubmit,
    formState: { errors },
  }: any = useForm({
    resolver: yupResolver(inputSchema),
  });

  async function getCurrentWeather(cityName: string): Promise<any> {
    setLoading(true);
    const response = await fetch(
      `https://api.weatherapi.com/v1/current.json?key=${WeatherApiVariables.token}&q=${cityName}&${WeatherApiVariables.airQuality}&${WeatherApiVariables.dataLanguage}`
    );
    const data = await response.json();
    return data;
  }

  async function getWeatherForecast(cityName: string): Promise<any> {
    setLoading(true);
    const response = await fetch(
      `https://api.weatherapi.com/v1/forecast.json?key=${WeatherApiVariables.token}&q=${cityName}&${WeatherApiVariables.airQuality}&${WeatherApiVariables.dataLanguage}&days=${WeatherApiVariables.forecastDays}`
    );
    const data = await response.json();
    return data;
  }

  useEffect(() => {
    async function fetchData() {
      const currentWeather = await getCurrentWeather(cityName);
      const weatherForecast = await getWeatherForecast(cityName);
      setCityWeather(currentWeather);
      setCityWeatherForecast(weatherForecast);
      setLoading(false);
    }
    fetchData();
  }, [cityName]);

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
    console.log(value);
  };

  const executeSearch: FormEventHandler<HTMLFormElement> = (
    event: React.FormEvent<HTMLFormElement>
  ) => {
    event.preventDefault();

    const transformedText = value
      .normalize("NFD")
      .replace(/[\u0300-\u036f]/g, "");
    setCityName(transformedText);
    setValue("");
  };

  return (
    <section className={styles.main}>
      <div className={styles.search_container}>
        <h2>Busque por uma cidade</h2>
        <form
          onSubmit={handleSubmit(executeSearch)}
          className={styles.search_form}
        >
          <input
            type="text"
            name="searchField"
            onChange={handleChange}
            placeholder="Ex: Blumenau, Santa Catarina"
            {...register("searchField")}
          />
          <p>{errors.searchField?.message}</p>
          <input
            type="submit"
            className={styles.search_btn}
            value="Pesquisar"
          />
        </form>
      </div>

      <div className={styles.current_weather_section}>
        {loading ? (
          <Loader className="loader" type="roller" size={150} />
        ) : (
          <>
            {!cityWeather.location ? (
              ""
            ) : (
              <div className={styles.current_weather_container}>
                <h2> Condição climática atual em:</h2>
                <h3>
                  {cityWeather.location.name}, {cityWeather.location.region}
                </h3>
                <div className={styles.condition_container}>
                  <span>{cityWeather.current.condition.text}</span>
                  <img
                    alt="ícone da condição atual do tempo"
                    src={cityWeather.current.condition.icon}
                  ></img>
                </div>
                <span>Temperatura atual: {cityWeather.current.temp_c} °C</span>
                <span>
                  Sensação térmica: {cityWeather.current.feelslike_c} °C
                </span>
              </div>
            )}
          </>
        )}
      </div>

      {!cityWeatherForecast.forecast ? (
        ""
      ) : (
        <>
          <h2>Previsão para os próximos dias</h2>
          <section className={styles.weather_forecast_section}>
            {cityWeatherForecast.forecast.forecastday.map(
              (item: any, key: string) => {
                const dateUnix = (item.date_epoch + 86400) * 1000;
                const newDate = new Date(dateUnix);
                const forecastWeekDay = newDate.toLocaleString("pt-BR", {
                  weekday: "long",
                });
                const forecastDay = newDate.toLocaleString("pt-BR", {
                  day: "numeric",
                });
                return (
                  <ul key={key} className={styles.weather_forecast_list}>
                    <li>
                      <h2>
                        {forecastWeekDay} - {forecastDay}
                      </h2>
                      <div className={styles.forecast_condition_container}>
                        <span>{item.day.condition.text}</span>
                        <img
                          alt="ícone do clima"
                          src={item.day.condition.icon}
                        />
                      </div>
                      <p>
                        Poss. chuva: {item.day.daily_chance_of_rain} % -{" "}
                        {item.day.totalprecip_mm} mm
                      </p>
                      <p>
                        <img
                          src={lowTemperature}
                          alt="termômetro - temperatura baixa"
                        />
                        {item.day.mintemp_c} °C
                      </p>
                      <p>
                        <img
                          src={highTemperature}
                          alt="termômetro - temperatura alta"
                        />
                        {item.day.maxtemp_c} °C
                      </p>
                    </li>
                  </ul>
                );
              }
            )}
          </section>
        </>
      )}
    </section>
  );
}

修复方案

1. 修正executeSearch参数错误

react-hook-form的handleSubmit会把表单数据传给回调函数,而非事件对象。你之前写的event.preventDefault()自然会报错,而且handleSubmit已经自动阻止了表单默认提交行为,不需要手动调用。

修改executeSearch:

// 定义表单数据类型,替代any
type FormInputs = {
  searchField: string;
};

const executeSearch = (data: FormInputs) => {
  const transformedText = data.searchField
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "");
  setCityName(transformedText);
};

2. 移除自定义状态和事件处理

react-hook-form会自动管理输入框状态,你手动写的value和handleChange会和register生成的逻辑冲突,导致输入不更新。

删除以下代码:

const [value, setValue] = useState("");

// ...

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  setValue(event.target.value);
  console.log(value);
};

同时修改input组件,去掉onChange={handleChange}:

<input
  type="text"
  name="searchField"
  placeholder="Ex: Blumenau, Santa Catarina"
  {...register("searchField")}
/>

3. 优化useForm类型定义

给useForm指定类型,避免any:

const {
  register,
  handleSubmit,
  formState: { errors },
} = useForm<FormInputs>({
  resolver: yupResolver(inputSchema),
});

4. 避免空字符串触发请求(可选)

当前组件挂载时,cityName是空字符串,会直接发起无效请求。可以在useEffect里加判断:

useEffect(() => {
  async function fetchData() {
    if (!cityName) return; // 空字符串不请求
    setLoading(true);
    try {
      const currentWeather = await getCurrentWeather(cityName);
      const weatherForecast = await getWeatherForecast(cityName);
      setCityWeather(currentWeather);
      setCityWeatherForecast(weatherForecast);
    } catch (err) {
      console.error("请求天气数据失败:", err);
    } finally {
      setLoading(false);
    }
  }
  fetchData();
}, [cityName]);

内容的提问来源于stack exchange,提问作者Alex_Lima84

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 04:01:07