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

父组件useState状态丢失:子组件调用回调时表单值异常清空

店铺地址编辑表单字段清空问题排查

更新说明

这可能是Formik的Bug,由于Formik已有一段时间未更新,我已切换至react-hook-form。相关Issue记录编号为3716。

场景说明

我使用React、Formik和google-map-react实现商家编辑店铺地址的谷歌地图地点自动补全功能,包含三个组件:

  • EditStoreInfoPage:页面组件,包含EditStoreInfoForm
  • EditStoreInfoForm:表单组件,基于Formik开发,包含FormikAddressField
  • FormikAddressField:支持谷歌地点自动补全的表单字段

店铺信息由EditStoreInfoPage从后端获取后传递给子组件,当在FormikAddressField中输入新地址时,会调用父组件传入的handleStoreLocationUpdate回调。

问题描述

页面初始渲染正常,formValues已正确填充后端返回的数据,但完成地址输入后,表单中除店铺地址外的其他字段全部被清空。控制台显示handleStoreLocationUpdate已被调用,但打印的formValues里店铺字段值为空。按照预期,React的useState应该保留从后端获取的初始值,不清楚为何会被清空。

代码示例

EditStoreInfoPage

type EditStoreInfoPageProps = {
  storeIdentifier: string;
};
const EditStoreInfoPage = (props: EditStoreInfoPageProps) => {
  let navigate = useNavigate();

  const [formValues, setFormValues] = React.useState<StoreAttributes>({
    storeName: "",
    storeLocation: "",
    storeLocationLongitude: 0,
    storeLocationLatitude: 0,
  });

  // 获取店铺信息
  React.useEffect(() => {
    const user: CognitoUser | null = getCurrentBusinessAccountUser();

    if (!user) {
      Toast("店铺未找到!", "获取店铺信息失败!", "danger");
    } else {
      const storeIdentifier: string = user?.getUsername();
      getStoreInfo(storeIdentifier)
        .then((response) => {
          setFormValues({
            storeName: response?.storeName || "",  
            storeLocation: response?.storeLocation || "",
            storeLocationLatitude: response?.storeLocationLatitude!,
            storeLocationLongitude: response?.storeLocationLongitude!,
          });
        })
        .catch((error) =>
          Toast(
            "店铺未找到!",
            "获取店铺信息失败!",
            "danger"
          )
        );
    }
  }, []);

  const handleStoreLocationUpdate = (newStoreLocation: string) => {
    const geocoder = new window.google.maps.Geocoder();
    console.log("handleStoreLocationUpdate");
    console.log(newStoreLocation);
    console.log(formValues);

    const geocodeRequest = { address: newStoreLocation };
    const geocodeCallback = (
      results: google.maps.GeocoderResult[] | null,
      status: google.maps.GeocoderStatus
    ) => {
      if (status === "OK") {
        if (results && results[0]) {
          const formValuesClone: StoreAttributes = structuredClone(formValues);
          formValuesClone.storeLocation = newStoreLocation;
          formValuesClone.storeLocationLatitude = results[0].geometry.location.lat();
          formValuesClone.storeLocationLongitude = results[0].geometry.location.lng();
          setFormValues(formValuesClone);
        } else {
          Toast("地址无效!", "请输入有效的地址", "danger");
        }
      } else {
        Toast("地址无效!", "请输入有效的地址", "danger");
      }
    };

    geocoder.geocode(geocodeRequest, geocodeCallback);
  };

  const handleSubmit = (data: StoreAttributes) => {
    updateStore(props.storeIdentifier, JSON.stringify(data, null, 2))
      .then((response) => {
        if (response.status == 200) {
          Toast(
            "更新成功!",
            "店铺信息已更新,正在跳转至店铺页面...",
            "success"
          );

          navigate("/stores/" + props.storeIdentifier);
        } else {
          Toast(
            "更新失败!",
            "更新店铺信息失败。",
            "danger"
          );
        }
      })
      .catch((error) => {
        Toast("更新失败!", error.message, "danger");
      });
  };

  const handleUpdate = (data: StoreAttributes) => {
    // 此处深拷贝,因为formValues是对象
    console.log("handleUpdate");
    const copy = structuredClone(data);
    setFormValues(copy);
  };

  return (
    <EditStoreInfoForm
      formValues={formValues}
      handleStoreLocationUpdate={handleStoreLocationUpdate}
      handleUpdate={handleUpdate}
      handleSubmit={handleSubmit}
    />
  );
};

export default EditStoreInfoPage;

EditStoreInfoForm

export type EditStoreInfoFormProps = {
  formValues: StoreAttributes;
  handleStoreLocationUpdate: any;
  handleUpdate: any;
  handleSubmit: any;
};

const EditStoreInfoForm = (props: EditStoreInfoFormProps) => {

 console.log("EditStoreInfoForm");
  const onBlur = () => {
    console.log(props.formValues);
  }

  return (
    <div className="flex justify-center items-center">
      
      <Formik.Formik
        initialValues={props.formValues}
        enableReinitialize={true}
        validationSchema={validationSchema}
        validateOnChange={false}
        validateOnBlur={false}
        onSubmit={(values) => {
          props.handleSubmit(values);
        }}
      >
        {({ }) => (
          <Formik.Form className="w-1/3">
            <div className="form-group">
            <div>
              <FormikTextField
                label="店铺名称"
                name="storeName"
                placeholder={props.formValues?.storeName}
              />
            </div>
            <div className="form-group">
              <FormikAddressField
                label="店铺地址"
                name="storeLocation"
                onAddressUpdate={props.handleStoreLocationUpdate}
                placeholder={props.formValues?.storeLocation}
              />
            </div>
            <div className="w-full  h-60">
              {/* <GoogleMapLocationPin latitude={10} longitude={10} text="store"/> */}
              <StoresGoogleMapLocation
                googleMapCenter={{
                  lat: props.formValues.storeLocationLatitude,
                  lng: props.formValues.storeLocationLongitude,
                }}
                storeAddress={props.formValues?.storeLocation}
                storeLocationLongitude={props.formValues?.storeLocationLongitude}
                storeLocationLatitude={props.formValues?.storeLocationLatitude}
              />
            </div>
        
            <div className="form-group">
              <button type="submit" className="form-button m-2 w-20 h-10">
                更新
              </button>
            </div>
          </Formik.Form>
           )}
                
      </Formik.Formik>
    </div>
 
  );
};

export default EditStoreInfoForm;

FormikAddressField

const FormikAddressField = ({ label, onAddressUpdate, ...props }: any) => {

  const [field, meta] = useField(props);

  const loader = new Loader({
    apiKey: process.env.REACT_APP_GOOGLE_MAP_API_KEY!,
    libraries: ["places", "geometry"],
  });

  const locationInputId = "locationInputId";
  let searchInput: HTMLInputElement;
  const autoCompleteInstanceRef = React.useRef<any>(null);

  React.useEffect(() => {
    loader.load().then(() => {
      let searchInput = document.getElementById(locationInputId) as HTMLInputElement;
      //console.log(searchInput);
      autoCompleteInstanceRef.current = new google.maps.places.Autocomplete(searchInput!, {
        // 限制搜索结果类型
        //types: ["address"],
        // 限制搜索国家,可传入数组
        // componentRestrictions: { country: ['gb', 'us'] },
      });

      autoCompleteInstanceRef.current.addListener("place_changed", onPlaceChanged);
    });

    // 组件卸载时清理监听
    return () => {
      google.maps.event.clearInstanceListeners(searchInput!);
    };
  }, []);

  const onPlaceChanged = () => {
    const place: google.maps.places.PlaceResult = autoCompleteInstanceRef.current.getPlace();
    if (!place) return;
    onAddressUpdate(place.formatted_address);
  };

  return (
    <>
      <label htmlFor={props.id || props.name} className="form-label">
        {label}
      </label>
      <Field
        id={locationInputId}
        className="text-md w-full h-full m-0 p-0"
        type="text"
        {...field}
        {...props}
      />
      {meta.touched && meta.error ? (
        <div className="error">{meta.error}</div>
      ) : null}
    </>
  );
};

export default FormikAddressField;

在线示例

存在简化版本的可运行示例。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 12:15:33