我正在使用 Material-UI (MUI) 开发一个 React.js 项目,并且我有一个组件,用户需要在其中输入他们的地址。为了最大限度地降低 API 成本,我希望仅在用户单击按钮时触发 Google Places API,而不是在每次输入更改时触发。
目前,我已将其设置为单击按钮时会发生 API 调用,但我遇到了两个问题:
点击按钮后,Google 地方信息自动完成下拉列表不会立即出现;仅当用户再次单击输入字段时才会显示。
即使在实现此功能之后,Google API 仍会继续对每次输入更改发出请求,这是我试图避免的。
如何配置该组件,以便在单击按钮时仅触发一次 Google Places API,并立即显示自动完成下拉列表,而无需任何额外的点击或不需要的 API 调用?
import React, { useState, useEffect, useRef } from "react";
import {
useMediaQuery,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
CircularProgress,
Fade,
FormControlLabel,
Checkbox,
} from "@mui/material";
function Modal({ open, setOpen }) {
const isUnder600px = useMediaQuery("(max-width:600px)");
const isUnder1000px = useMediaQuery("(max-width:1000px)");
const licensePlateInputRef = useRef(null);
const addressInputRef = useRef(null);
const autocompleteRef = useRef(null);
const [data, setData] = useState({
licensePlate: "",
address: "",
});
const [loading, setLoading] = useState(false);
const [showForm, setShowForm] = useState(false);
const [isLicensePlateInputDisabled, setIsLicensePlateInputDisabled] = useState(false);
const [showCloseButton, setShowCloseButton] = useState(true);
const [inputKey, setInputKey] = useState(Date.now());
let maxWidth;
if (isUnder600px) {
maxWidth = "xs";
} else if (isUnder1000px) {
maxWidth = "sm";
} else {
maxWidth = "md";
}
useEffect(() => {
if (open && licensePlateInputRef.current) {
licensePlateInputRef.current.focus();
}
}, [open]);
const handleClose = (event, reason) => {
if (reason && reason === "backdropClick") return;
setOpen(false);
};
const handleChange = (e) => {
const { name, value } = e.target;
setData((prev) => ({
...prev,
[name]: value,
}));
};
const handleSearch = () => {
setLoading(true);
setShowCloseButton(false);
setTimeout(() => {
setLoading(false);
setShowForm(true);
setIsLicensePlateInputDisabled(true);
}, 1500);
};
const handleSearchAddress = () => {
setInputKey(Date.now());
if (
window.google &&
addressInputRef.current &&
!autocompleteRef.current
) {
autocompleteRef.current = new window.google.maps.places.Autocomplete(
addressInputRef.current,
{
types: ["address"],
componentRestrictions: { country: "it" },
}
);
autocompleteRef.current.addListener("place_changed", () => {
const place = autocompleteRef.current.getPlace();
if (place && place.formatted_address) {
setData((prev) => ({
...prev,
address: place.formatted_address,
}));
}
});
addressInputRef.current.focus();
const inputEvent = new Event("input", { bubbles: true });
addressInputRef.current.dispatchEvent(inputEvent);
}
};
const handleSubmit = (e) => {
e.preventDefault();
handleSearch();
};
return (
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
fullWidth={true}
maxWidth={maxWidth}
PaperProps={{
sx: {
maxWidth: showForm ? maxWidth : "400px",
width: "100%",
overflow: "visible",
},
}}
>
<DialogTitle id="form-dialog-title">
{loading ? "Searching..." : "New Search"}
</DialogTitle>
<form onSubmit={handleSubmit}>
<DialogContent>
{loading ? (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "8px 0",
}}
>
<CircularProgress />
</Box>
) : (
<>
<Box
sx={{
display: "flex",
flexDirection: maxWidth === "md" ? "row" : "column",
marginBottom: "8px",
marginTop: "8px",
}}
>
<TextField
id={"licensePlate-input"}
disabled={isLicensePlateInputDisabled}
size="small"
label="License Plate"
name="licensePlate"
value={data.licensePlate}
onChange={handleChange}
fullWidth={!showForm}
inputRef={licensePlateInputRef}
autoFocus
/>
{showForm && (
<Fade in={showForm}>
<Box
sx={{
display: "flex",
flexDirection: "column",
marginTop: maxWidth === "md" ? "0" : "8px",
}}
>
<Box ml={maxWidth === "md" ? 4 : 0}>
<FormControlLabel
control={
<Checkbox
sx={{
marginBottom: "3px",
margin: maxWidth === "xs" ? "8px 0" : " 0",
}}
defaultChecked={false}
/>
}
label="Via Roma 56,199, Giugliano in Campania Na"
/>
<Box sx={{ display: "flex", flexDirection: "row" }}>
<TextField
id={`address-input-${inputKey}`}
size="small"
label="Residential Address"
sx={{ width: "360px", marginRight: "16px" }}
error={false}
helperText={""}
value={data.address}
onChange={handleChange}
name="address"
InputProps={{
inputRef: addressInputRef,
}}
/>
<Button
sx={{ maxHeight: "36px" }}
variant="contained"
onClick={handleSearchAddress}
>
{(maxWidth === "md") | "sm"
? "Search Address"
: "Search"}
</Button>
</Box>
</Box>
</Box>
</Fade>
)}
</Box>
</>
)}
</DialogContent>
<DialogActions>
</DialogActions>
</form>
</Dialog>
);
}
export default Modal;
你尝试下面的代码
import React, { useState, useEffect, useRef } from "react";
import {
useMediaQuery,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
CircularProgress,
Fade,
FormControlLabel,
Checkbox,
} from "@mui/material";
function Modal({ open, setOpen }) {
const isUnder600px = useMediaQuery("(max-width:600px)");
const isUnder1000px = useMediaQuery("(max-width:1000px)");
const licensePlateInputRef = useRef(null);
const addressInputRef = useRef(null);
const autocompleteRef = useRef(null);
const [data, setData] = useState({
licensePlate: "",
address: "",
});
const [loading, setLoading] = useState(false);
const [showForm, setShowForm] = useState(false);
const [isLicensePlateInputDisabled, setIsLicensePlateInputDisabled] = useState(false);
const [showCloseButton, setShowCloseButton] = useState(true);
const [inputKey, setInputKey] = useState(Date.now());
let maxWidth;
if (isUnder600px) {
maxWidth = "xs";
} else if (isUnder1000px) {
maxWidth = "sm";
} else {
maxWidth = "md";
}
useEffect(() => {
if (open && licensePlateInputRef.current) {
licensePlateInputRef.current.focus();
}
}, [open]);
const handleClose = (event, reason) => {
if (reason && reason === "backdropClick") return;
setOpen(false);
};
const handleChange = (e) => {
const { name, value } = e.target;
setData((prev) => ({
...prev,
[name]: value,
}));
};
const handleSearch = () => {
setLoading(true);
setShowCloseButton(false);
setTimeout(() => {
setLoading(false);
setShowForm(true);
setIsLicensePlateInputDisabled(true);
}, 1500);
};
const handleSearchAddress = () => {
setInputKey(Date.now()); // Force re-render
if (window.google && addressInputRef.current) {
if (!autocompleteRef.current) {
autocompleteRef.current = new window.google.maps.places.Autocomplete(
addressInputRef.current,
{
types: ["address"],
componentRestrictions: { country: "it" },
}
);
autocompleteRef.current.addListener("place_changed", () => {
const place = autocompleteRef.current.getPlace();
if (place && place.formatted_address) {
setData((prev) => ({
...prev,
address: place.formatted_address,
}));
}
});
}
// Trigger the autocomplete dropdown manually
const inputEvent = new Event("input", { bubbles: true });
addressInputRef.current.dispatchEvent(inputEvent);
// Set focus to the input field to ensure the dropdown shows up
addressInputRef.current.focus();
}
};
const handleSubmit = (e) => {
e.preventDefault();
handleSearch();
};
return (
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
fullWidth={true}
maxWidth={maxWidth}
PaperProps={{
sx: {
maxWidth: showForm ? maxWidth : "400px",
width: "100%",
overflow: "visible",
},
}}
>
<DialogTitle id="form-dialog-title">
{loading ? "Searching..." : "New Search"}
</DialogTitle>
<form onSubmit={handleSubmit}>
<DialogContent>
{loading ? (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "8px 0",
}}
>
<CircularProgress />
</Box>
) : (
<>
<Box
sx={{
display: "flex",
flexDirection: maxWidth === "md" ? "row" : "column",
marginBottom: "8px",
marginTop: "8px",
}}
>
<TextField
id={"licensePlate-input"}
disabled={isLicensePlateInputDisabled}
size="small"
label="License Plate"
name="licensePlate"
value={data.licensePlate}
onChange={handleChange}
fullWidth={!showForm}
inputRef={licensePlateInputRef}
autoFocus
/>
{showForm && (
<Fade in={showForm}>
<Box
sx={{
display: "flex",
flexDirection: "column",
marginTop: maxWidth === "md" ? "0" : "8px",
}}
>
<Box ml={maxWidth === "md" ? 4 : 0}>
<FormControlLabel
control={
<Checkbox
sx={{
marginBottom: "3px",
margin: maxWidth === "xs" ? "8px 0" : " 0",
}}
defaultChecked={false}
/>
}
label="Via Roma 56,199, Giugliano in Campania Na"
/>
<Box sx={{ display: "flex", flexDirection: "row" }}>
<TextField
id={`address-input-${inputKey}`}
size="small"
label="Residential Address"
sx={{ width: "360px", marginRight: "16px" }}
error={false}
helperText={""}
value={data.address}
onChange={handleChange}
name="address"
InputProps={{
inputRef: addressInputRef,
}}
/>
<Button
sx={{ maxHeight: "36px" }}
variant="contained"
onClick={handleSearchAddress}
>
{(maxWidth === "md" || maxWidth === "sm") ? "Search Address" : "Search"}
</Button>
</Box>
</Box>
</Box>
</Fade>
)}
</Box>
</>
)}
</DialogContent>
<DialogActions>
</DialogActions>
</form>
</Dialog>
);
}
export default Modal;