上下文
我使用React,Formik,和google-map-react,让店主编辑他们的商店地址与谷歌Map的地方自动完成。
我有三个组成部分:
EditStoreInfoPage
是包含EditStoreInfoForm
的页面组件。EditStoreInfoForm
是表单组件,它包含FormikAddressField
。FormikAddressField
是一个支持GooglePlace自动完成的表单字段。
存储信息将从EditStoreInfoPage
中的后端获取,并向下传递到EditStoreInfoForm
和FormikAddressField
。每当在FormikAddressField
中键入新地址时,它将调用从EditStoreInfoPage
向下传递的回调函数handleStoreLocationUpdate
。
问题
呈现页面没有任何问题。我看到formValues填充了从后端获取的数据。
然而,一旦我打完地址,表格就会被清除,除了商店地址还在。
从上面屏幕截图的控制台输出中,我可以看到函数handleStoreLocationUpdate
被调用,但是,EditStoreInfoPage
的函数handleStoreLocationUpdate
中的console.log(formValues);
包含存储字段的空值。我期望这里的formValues
仍然保留从后端获取的值,不确定为什么这些值在我使用React useState
时被清除。
知道哪里出了问题吗?
代码
编辑存储信息页面
这是React组件,它首先调用后端API以基于storeIdentifier获取存储信息。formValues
将使用这些信息填充,正如您所看到的,setFormValues
正在被调用。formValues
将作为属性传递给子组件EditStoreInfoForm
。
type EditStoreInfoPageProps = {
storeIdentifier: string;
};
const EditStoreInfoPage = (props: EditStoreInfoPageProps) => {
let navigate = useNavigate();
const [formValues, setFormValues] = React.useState<StoreAttributes>({
storeName: "",
storeLocation: "",
storeLocationLongitude: 0,
storeLocationLatitude: 0,
});
// Get store info.
React.useEffect(() => {
const user: CognitoUser | null = getCurrentBusinessAccountUser();
if (!user) {
Toast("Store Not Found!", "Failed to get store information!", "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(
"Store Not Found!",
"Failed to get store information!",
"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("Not valid address!", "Please input a valid address", "danger");
}
} else {
Toast("Not valid address!", "Please input a valid address", "danger");
}
};
geocoder.geocode(geocodeRequest, geocodeCallback);
};
const handleSubmit = (data: StoreAttributes) => {
updateStore(props.storeIdentifier, JSON.stringify(data, null, 2))
.then((response) => {
if (response.status == 200) {
Toast(
"Updated!",
"The store information has been updated. Redirect to store page...",
"success"
);
navigate("/stores/" + props.storeIdentifier);
} else {
Toast(
"Updated failed!",
"Failed to update store information.",
"danger"
);
}
})
.catch((error) => {
Toast("Updated failed!!", error.message, "danger");
});
};
const handleUpdate = (data: StoreAttributes) => {
// make a deep clone here, as formValues here is an object.
console.log("handleUpdate");
const copy = structuredClone(data);
setFormValues(copy);
};
return (
<EditStoreInfoForm
formValues={formValues}
handleStoreLocationUpdate={handleStoreLocationUpdate}
handleUpdate={handleUpdate}
handleSubmit={handleSubmit}
/>
);
};
export default EditStoreInfoPage;
编辑存储信息表单
EditStoreInfoForm
是表单组件。我在这里使用Formik。它用props.formValues
呈现表单。它包含一个子组件FormikAddressField
,将用于支持google place自动完成。
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="Store Name"
name="storeName"
placeholder={props.formValues?.storeName}
/>
</div>
<div className="form-group">
<FormikAddressField
label="Store Location"
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">
Update
</button>
</div>
</Formik.Form>
)}
</Formik.Formik>
</div>
);
};
export default EditStoreInfoForm;
表单地址字段
FormikAddressField
是用于自动完成的字段。请参阅https://developers.google.com/maps/documentation/javascript/place-autocomplete以了解它是什么。
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!,
{
// restrict your search to a specific type of resultmap
//types: ["address"],
// restrict your search to a specific country, or an array of countries
// componentRestrictions: { country: ['gb', 'us'] },
}
);
autoCompleteInstanceRef.current.addListener(
"place_changed",
onPlaceChanged
);
});
// returned function will be called on component unmount
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;
1条答案
按热度按时间ds97pgxw1#
EditStoreInfoPage
在EditStoreInfoForm
之上。EditStoreInfoPage
中的formikValues
看起来像是一个副本,它并不是每次EditStoreInfoForm
中的实际实时formik值改变时都更新的。这里真正的问题是,首先不应该有克隆。只需将实际存储值向上传递给处理程序:
现在更改:
收件人:
运用这个论点。
如前所述,这里还有其他问题。你真的应该重构来完全摆脱这个问题:
您可以通过使实际的表单状态可由该组件访问来实现这一点,可能是通过更改为
useFormik
模式并在父对象中加载该钩子。