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方案,逻辑更统一:
- 新增途经点的ref:
const stopRef = useRef(null);
- 修改途经点input的属性,移除
onChange,添加ref:
<Autocomplete className={styles.input}> <input type="text" placeholder="Stopping place" required ref={stopRef} /> </Autocomplete>
- 更新
wayptAddress的生成逻辑,从ref中取值:
const wayptAddress = stopRef.current?.value ? [ { location: stopRef.current.value, stopover: true, }, ] : [];
- 修改
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
相关产品推荐
相关产品推荐

