在收到道具之前反应渲染

React Renders Before Props Recieved

我正在使用 MapBox 在用户个人资料上显示地图。该组件接收位置道具,但 mapbox 尝试在收到道具之前使用位置渲染地图,从而破坏了页面。我已经使用了一种解决方法来使其工作......但不确定我是否做对了 - 你有什么建议吗?我的解决方法是使用 useEffect Hook 设置 viewPort 状态...这会强制地图在加载之前等待一秒钟,以便有时间接收道具。我有一种感觉,如果没有及时收到道具,它仍然会崩溃。

我也想使用用户的位置作为地图的中心点,目前正在 useEffect 挂钩中设置 - 但在设置之前我无法获取道具。谢谢!

    import React, { useState, useEffect } from 'react';
import Geocode from "react-geocode";
import ReactMapGL, { Marker } from 'react-map-gl';
import { Label, Input, Card, Badge, Button, InputGroup,
} from 'reactstrap';

// set response language. Defaults to english.
Geocode.setLanguage("en");

// set response region. Its optional.
// A Geocoding request with region=es (Spain) will return the Spanish city.
Geocode.setRegion("es");

// set Google Maps Geocoding API for purposes of quota management. Its optional but recommended.
Geocode.setApiKey("******************************");

export default function Location(props) {
    const [ city, setCity ] = useState('');
    const [ viewPort, setViewport ] = useState({})

    useEffect(() => {
        setTimeout(() => {
            setViewport({
                latitude: 37.0902,
                longitude: -95.7129,
                width: '100%',
                height: '200px',
                zoom: 3
            })
        },1000)

    }, [])

    const captureCity = (e) => {
        setCity(e.target.value);
    }

    const handleLocationInput = () => {
        // Get latidude & longitude from address.
        Geocode.fromAddress(city).then(
            response => {
            const { lat, lng } = response.results[0].geometry.location;
            putLocation(lat, lng);
            },
            error => {
            console.error(error);
            }
        );
    }

    const putLocation = (lat, lon) => {
        props.updateBandLocation([lat,lon]);
    }

    return (
        <div>
            <div className="w-100">
                <ReactMapGL 
                    className="mt-2 mb-4 rounded"
                    style={{zIndex: "2"}}
                    {...viewPort} 
                    mapboxApiAccessToken={"******************"}
                    onViewportChange={viewport => setViewport(viewport)}
                    mapStyle="mapbox://styles/nickisyourfan/ck8ylgfk90kcb1iqoemkes76u"
                    >
                            <Marker latitude={props.bandLocation[0]} longitude={props.bandLocation[1]}>

                                <img src="/MapMarker.svg" alt="Band Icon" style={{width: "35px", height: "35px"}}/>

                            </Marker>

                </ReactMapGL> 
            </div>

            <Card color="dark" className="mb-1">
                <InputGroup className="d-flex flex-column justify-content-center">
                    <Label className="d-flex flex-column text-light h3 mt-2 align-self-center">Location</Label>
                    <Input type="text" onChange={captureCity} className="d-flex align-self-center w-75 mb-2"/>
                    <Button onClick={handleLocationInput} className="w-75 align-self-center mb-2">Set Location</Button>
                </InputGroup>
            </Card>
        </div>
    )
}

我不会在这种情况下使用 setTimeout。

在获得地图的所有预期数据之前,我不会渲染地图。 我会将其添加到我的 return 声明之上。

if (!props.bandLocation.length || !props.bandLocation[0] || !props.bandLocation[1]) {
return <MyCustomLoadingComp />
}

在你的具体情况下,我不确定地图究竟需要哪个道具或数据结构,但你可以理解这个概念。例如,如果 props.bandLocation[0] 是一个空对象,它仍然会标记为真,因此您必须更改 if 语句以匹配您正在接收的数据。

更新: 您可能想要使用类似 'usePrevious' 辅助函数的东西。与 componentWillReceiveProps 类似,只有当您获得某个项目时,您才需要更新 UI 中的内容。所以在这种情况下,你可以检查你是否得到一个有效的 lat/long,然后用 useState 设置它。我需要用钩子复制这个生命周期方法,我改编自这个 https://usehooks.com/usePrevious/。不过还是建议,直接使用props就可以了。您可以将道具的名称传递给useEffect,当它更改时,comp 将重新渲染。

所以

const [loaded, setLoaded] = useState(lat);
  useEffect(() => {
setLoaded(false);
if (isAValidLocation(props.lat)) {
  setLoaded(true);

}
}, [props.lat, props.long]);

然而,直言不讳,你不应该渲染你的地图,直到它有所有相关数据,如果你不以任何方式操纵道具内容,你不应该需要使用任何生命周期挂钩。