触发google将api放在按钮点击而不是输入更改上

问题描述 投票:0回答:1

我正在使用 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;


reactjs material-ui google-places-api
1个回答
0
投票

你尝试下面的代码

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;
© www.soinside.com 2019 - 2024. All rights reserved.