I’m working on a React.js project using Material-UI (MUI), and I have a component where users need to enter their address. To minimize API costs, I want to trigger the Google Places API only when the user clicks a button, rather than on every input change.
Currently, I’ve set it up so that the API call happens when the button is clicked, but I’m running into two issues:
The Google Places autocomplete dropdown doesn’t appear immediately after the button click; it only shows up if the user clicks the input field again.
Even after implementing this, the Google API continues to make requests on every input change, which is what I’m trying to avoid.
How can I configure the component so that the Google Places API is triggered only once when the button is clicked, and the autocomplete dropdown shows up immediately, without any additional clicks or unwanted API calls?
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 [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);
setTimeout(() => {
setLoading(false);
setShowForm(true);
setIsLicensePlateInputDisabled(true);
}, 1500);
};
const handleSearchAddress = () => {
setInputKey(Date.now()); // Update input key
if (
window.google &&
addressInputRef.current &&
!autocompleteRef.current
) {
autocompleteRef.current = new window.google.maps.places.Autocomplete(
addressInputRef.current,
{
types: ["address"],
componentRestrictions: { country: "it" },
}
);
// **Immediate Trigger (1): Set placeId to empty string**
addressInputRef.current.placeId = "";
// **Immediate Trigger (2): Simulate user input event**
const inputEvent = new Event("input", { bubbles: true });
addressInputRef.current.dispatchEvent(inputEvent);
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(); // Focus the input field
}
};
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;