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

React+Google Maps API问题:途经点输入无法显示Autocomplete选中地址

问题:途经点Autocomplete选中地址后输入框无法显示内容

我用@react-google-maps/api结合Google Maps API开发了一个带起点、终点、途经点的地图应用。起点和终点输入框的Places API Autocomplete功能正常,选中地址后能正确显示,但途经点输入框从Autocomplete下拉菜单选中地址后,输入框无法展示选中的内容。


我的代码

React 代码

import { React, useRef, useState, Fragment } from "react";
import styles from "./RouteForm.module.css";
import KeyboardDoubleArrowDownRoundedIcon from "@mui/icons-material/KeyboardDoubleArrowDownRounded";
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
import PlaceIcon from "@mui/icons-material/Place";
import AddCircleIcon from "@mui/icons-material/AddCircle";
import {
  GoogleMap,
  useJsApiLoader,
  Autocomplete,
  DirectionsRenderer,
} from "@react-google-maps/api";

const libraries = ["places"];

const RouteForm = (props) => {
  const originRef = useRef(null);
  const destinationRef = useRef(null);

  const [stopValue, setStopValue] = useState(null);
  const [stopContainerVisible, setStopContainerVisible] = useState(false);
  const [directionsRes, setDirectionsRes] = useState(null);
  const [routeFormVisible, setRouteFormVisible] = useState(false);

  const handleRouteSubmit = (e) => {
    e.preventDefault();

    console.log(
      `Origin: ${originRef.current.value}, Destination: ${destinationRef.current.value}, Waypoint: ${stopValue}`
    );
  };

  const toggleFormVisibility = () => {
    setRouteFormVisible(!routeFormVisible);
  };

  const { isLoaded } = useJsApiLoader({
    libraries: libraries,
  });

  const toggleStopVisibility = () => {
    setStopContainerVisible((prevValue) => !prevValue);
    console.log(stopContainerVisible);
  };

  const center = { lat: 42.4414, lng: 19.2624 };

  if (!isLoaded) {
    return (
      <div>
        <p>Loading...</p>
      </div>
    );
  }

  const wayptAddress = stopValue
    ? [
        {
          location: stopValue,
          stopover: true,
        },
      ]
    : [];

  async function calculateRoute() {
    if (originRef.current.value === "" || destinationRef.current.value === "") {
      return;
    }

    // eslint-disable-next-line no-undef
    const directionService = new google.maps.DirectionsService();
    const results = await directionService.route({
      origin: originRef.current.value,
      destination: destinationRef.current.value,
      // eslint-disable-next-line no-undef
      travelMode: google.maps.TravelMode.DRIVING,
      waypoints: wayptAddress,
    });

    setDirectionsRes(results);
  }

  return (
    <Fragment>
      <div className={styles["form-and-map-container"]}>
        <GoogleMap
          center={center}
          zoom={15}
          mapContainerStyle={{
            width: "100%",
            height: "100%",
          }}
          options={{
            zoomControl: false,
            streetViewControl: false,
            mapTypeControl: false,
            fullscreenControl: false,
          }}
        >
          {directionsRes && <DirectionsRenderer directions={directionsRes} />}
        </GoogleMap>

        <div className={styles.container}>
          <div className={styles.expand} onClick={toggleFormVisibility}>
            {routeFormVisible ? (
              <CloseRoundedIcon
                fontSize="inherit"
                className={styles["expand-icon"]}
              />
            ) : (
              <KeyboardDoubleArrowDownRoundedIcon
                fontSize="inherit"
                className={styles["expand-icon"]}
              />
            )}
            <span>Create Route</span>
          </div>
          <div className={styles["form-container"]}>
            {routeFormVisible && (
              <form onSubmit={handleRouteSubmit}>
                <div className={styles.inputs}>
                  <div className={styles["input1-container"]}>
                    <label>
                      <PlaceIcon />
                    </label>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Origin"
                        ref={originRef}
                        required
                      />
                    </Autocomplete>
                  </div>
                  <div className={styles["input2-container"]}>
                    <label>
                      <PlaceIcon sx={{ color: "#beff0a" }} />
                    </label>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Destination"
                        ref={destinationRef}
                        required
                      />
                    </Autocomplete>
                  </div>
                </div>
                <div className={styles.btns}>
                  <button type="submit" onClick={calculateRoute}>
                    Create Route
                  </button>
                  <span
                    className={styles["add-stop"]}
                    onClick={toggleStopVisibility}
                  >
                    Add Stop
                  </span>
                </div>
                {stopContainerVisible && (
                  <div className={styles["stop-container"]}>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Stopping place"
                        required
                        onChange={(e) => setStopValue(e.target.value)}
                      />
                    </Autocomplete>
                    <button type="submit" className={styles["stop-submit-btn"]}>
                      <AddCircleIcon />
                    </button>
                  </div>
                )}
              </form>
            )}
          </div>
        </div>
      </div>
    </Fragment>
  );
};

export default RouteForm;

CSS 代码

@font-face {
  font-family: "poppinsregular";
  src: url("./../assets/fonts/poppins-regular-webfont.woff2") format("woff2"),
    url("./../assets/fonts/poppins-regular-webfont.woff") format("woff");
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: "poppinsitalic";
  src: url("./../assets/fonts/poppins-italic-webfont.woff2") format("woff2"),
    url("./../assets/fonts/poppins-italic-webfont.woff") format("woff");
  font-weight: normal;
  font-style: normal;
}

* {
  box-sizing: border-box;
}

.form-and-map-container {
  position: absolute;
  width: 100%;
  height: 100vh;
}

.container {
  padding: 1rem 1.2rem;
  font-family: "poppinsregular", sans-serif;
  display: inline-block;
  width: 20%;
  background-color: #fffdf7;
  border-radius: 15px;
  -webkit-box-shadow: -1px 6px 18px -9px rgba(66, 68, 90, 0.53);
  -moz-box-shadow: -1px 6px 18px -9px rgba(66, 68, 90, 0.53);
  box-shadow: -1px 6px 18px -9px rgba(66, 68, 90, 0.53);
  position: fixed;
  top: 3rem;
  left: 3rem;
  height: auto;
}

.expand {
  font-size: 1.4rem;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-bottom: 1rem;
  cursor: pointer;
  color: #0a2342;
  margin-bottom: 0;
}

.expand-icon {
  font-size: inherit;
  margin-right: 0.4rem;
}

.input1-container,
.input2-container,
.stop-container {
  color: #0a2342;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-top: 1rem;
  width: 100%;
}

.input1-container label,
.input2-container label,
.stop-container label {
  margin-right: 0.4rem;
  width: auto;
}

.input {
  display: flex;
  width: 100%;
}

.input1-container input,
.input2-container input,
.stop-container input {
  font-family: "poppinsitalic", sans-serif;
  background-color: transparent;
  border: solid 2px #0000000b;
  padding: 0.5rem 0.6rem;
  border-radius: 5px;
  font-size: 0.8rem;
  display: inline;
  width: 100%;
}

.input1-container input:focus,
.input2-container input:focus,
.stop-container input:focus {
  outline: none;
}

.btns {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}

.btns button,
.btns .add-stop {
  margin-top: 1.5rem;
  font-family: "poppinsregular", sans-serif;
  outline: none;
  background-color: #beff0a;
  color: #0a2342;
  border: none;
  border-radius: 10px;
  padding: 0.7rem 0.7rem;
  cursor: pointer;
  width: 40%;
  text-align: center;
}

.btns button:first-of-type {
  width: 55%;
  background-color: #0a2342;
  color: #fffdf7;
}

.stop-submit-btn {
  background-color: transparent;
  border: none;
  color: #0a2342;
  cursor: pointer;
  width: 20%;
}

解决方案

问题根源:途经点输入框使用useState配合onChange管理值,但Autocomplete组件选中下拉地址时,会直接修改input的DOM值,不会触发onChange事件,导致stopValue状态和实际input显示值不同步,所以输入框无法展示选中的地址。

推荐采用和起点、终点一致的useRef方案,逻辑更统一:

  1. 新增途经点的ref:
const stopRef = useRef(null);
  1. 修改途经点input的属性,移除onChange,添加ref:
<Autocomplete className={styles.input}>
  <input
    type="text"
    placeholder="Stopping place"
    required
    ref={stopRef}
  />
</Autocomplete>
  1. 更新wayptAddress的生成逻辑,从ref中取值:
const wayptAddress = stopRef.current?.value
  ? [
      {
        location: stopRef.current.value,
        stopover: true,
      },
    ]
  : [];
  1. 修改handleRouteSubmit中的日志输出,从ref取值:
console.log(
  `Origin: ${originRef.current.value}, Destination: ${destinationRef.current.value}, Waypoint: ${stopRef.current?.value}`
);

修改后的完整React代码如下:

import { React, useRef, useState, Fragment } from "react";
import styles from "./RouteForm.module.css";
import KeyboardDoubleArrowDownRoundedIcon from "@mui/icons-material/KeyboardDoubleArrowDownRounded";
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
import PlaceIcon from "@mui/icons-material/Place";
import AddCircleIcon from "@mui/icons-material/AddCircle";
import {
  GoogleMap,
  useJsApiLoader,
  Autocomplete,
  DirectionsRenderer,
} from "@react-google-maps/api";

const libraries = ["places"];

const RouteForm = (props) => {
  const originRef = useRef(null);
  const destinationRef = useRef(null);
  const stopRef = useRef(null); // 新增途经点ref

  const [stopContainerVisible, setStopContainerVisible] = useState(false);
  const [directionsRes, setDirectionsRes] = useState(null);
  const [routeFormVisible, setRouteFormVisible] = useState(false);

  const handleRouteSubmit = (e) => {
    e.preventDefault();

    console.log(
      `Origin: ${originRef.current.value}, Destination: ${destinationRef.current.value}, Waypoint: ${stopRef.current?.value}`
    );
  };

  const toggleFormVisibility = () => {
    setRouteFormVisible(!routeFormVisible);
  };

  const { isLoaded } = useJsApiLoader({
    libraries: libraries,
  });

  const toggleStopVisibility = () => {
    setStopContainerVisible((prevValue) => !prevValue);
    console.log(stopContainerVisible);
  };

  const center = { lat: 42.4414, lng: 19.2624 };

  if (!isLoaded) {
    return (
      <div>
        <p>Loading...</p>
      </div>
    );
  }

  const wayptAddress = stopRef.current?.value
    ? [
        {
          location: stopRef.current.value,
          stopover: true,
        },
      ]
    : [];

  async function calculateRoute() {
    if (originRef.current.value === "" || destinationRef.current.value === "") {
      return;
    }

    // eslint-disable-next-line no-undef
    const directionService = new google.maps.DirectionsService();
    const results = await directionService.route({
      origin: originRef.current.value,
      destination: destinationRef.current.value,
      // eslint-disable-next-line no-undef
      travelMode: google.maps.TravelMode.DRIVING,
      waypoints: wayptAddress,
    });

    setDirectionsRes(results);
  }

  return (
    <Fragment>
      <div className={styles["form-and-map-container"]}>
        <GoogleMap
          center={center}
          zoom={15}
          mapContainerStyle={{
            width: "100%",
            height: "100%",
          }}
          options={{
            zoomControl: false,
            streetViewControl: false,
            mapTypeControl: false,
            fullscreenControl: false,
          }}
        >
          {directionsRes && <DirectionsRenderer directions={directionsRes} />}
        </GoogleMap>

        <div className={styles.container}>
          <div className={styles.expand} onClick={toggleFormVisibility}>
            {routeFormVisible ? (
              <CloseRoundedIcon
                fontSize="inherit"
                className={styles["expand-icon"]}
              />
            ) : (
              <KeyboardDoubleArrowDownRoundedIcon
                fontSize="inherit"
                className={styles["expand-icon"]}
              />
            )}
            <span>Create Route</span>
          </div>
          <div className={styles["form-container"]}>
            {routeFormVisible && (
              <form onSubmit={handleRouteSubmit}>
                <div className={styles.inputs}>
                  <div className={styles["input1-container"]}>
                    <label>
                      <PlaceIcon />
                    </label>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Origin"
                        ref={originRef}
                        required
                      />
                    </Autocomplete>
                  </div>
                  <div className={styles["input2-container"]}>
                    <label>
                      <PlaceIcon sx={{ color: "#beff0a" }} />
                    </label>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Destination"
                        ref={destinationRef}
                        required
                      />
                    </Autocomplete>
                  </div>
                </div>
                <div className={styles.btns}>
                  <button type="submit" onClick={calculateRoute}>
                    Create Route
                  </button>
                  <span
                    className={styles["add-stop"]}
                    onClick={toggleStopVisibility}
                  >
                    Add Stop
                  </span>
                </div>
                {stopContainerVisible && (
                  <div className={styles["stop-container"]}>
                    <Autocomplete className={styles.input}>
                      <input
                        type="text"
                        placeholder="Stopping place"
                        required
                        ref={stopRef} // 绑定ref
                      />
                    </Autocomplete>
                    <button type="submit" className={styles["stop-submit-btn"]}>
                      <AddCircleIcon />
                    </button>
                  </div>
                )}
              </form>
            )}
          </div>
        </div>
      </div>
    </Fragment>
  );
};

export default RouteForm;

这样修改后,途经点输入框的Autocomplete选中地址后会正常显示,和起点、终点的行为保持一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 23:51:42