cannot access state in componentDidMount

落花浮王杯 提交于 2021-01-29 21:34:45

问题


I am using library react-google-maps, and i want to use DirectionsRenderer between two nodes. Here is my state

this.state={
    currentPosition:{lat: 26.84 ,lng: 75.80},
    destinationPosition:{lat: 26.84 ,lng: 75.80},
};

I want to show the direction between my current location and marker. And my componentDidMount() is inside render method. Here is the code for render method

class map extends React.PureComponent{
    constructor(props){
        super(props);
        this.state={
            currentPosition:{lat: 26.84 ,lng: 75.80},
            destinationPosition:{lat: 26.84 ,lng: 75.80},
            direction:false
        };
    }
    onMarkerPositionChanged(e){
        this.setState((state)=>({
            destinationPosition:{lat:e.latLng.lat(),lng:e.latLng.lng()}}));
    }
    handleClick(){
        if(navigator.geolocation){
            navigator.geolocation.getCurrentPosition((position)=>{
                this.setState(()=>({
                    currentPosition:{lat:position.coords.latitude,lng:position.coords.longitude}}))
            }); 
        }
        else{
            alert("Geoloaction is not supported by your browser");
        }
    }
    changeDir(){
        if(this.state.direction)
            this.setState(()=>({direction:false}))
        else
            this.setState(()=>({direction:true}))
    }
    render(){
        const MyMapComponent = compose(
          withProps({
            googleMapURL: "https://maps.googleapis.com/maps/api/js?key=AIzaSyC5VMMlyr_A6K5ycpOrq3OsVM8YYbn0q3A&v=3.exp&libraries=geometry,drawing,places",
            loadingElement: <div style={{ height: `100%` }} />,
            containerElement: <div style={{ height: `300px` }} />,
            mapElement: <div style={{ height: `100%` }} />,
          }),
          withScriptjs,
          withGoogleMap,
          lifecycle({       
            componentDidMount() {
                const google=window.google;
                console.log(this.state);
//--->this statement prints null
                const DirectionsService = new google.maps.DirectionsService();
                DirectionsService.route({
                origin: new google.maps.LatLng(this.state.currentPosition.lat, this.state.currentPosition.lng),
                destination: new google.maps.LatLng(this.state.destinationPosition.lat,this.state.destinationPosition.lng),
//---->  this is where i want to use the state to get the direction between //current location and marker
                travelMode: google.maps.TravelMode.DRIVING,
                }, (result, status) => {
                if (status === google.maps.DirectionsStatus.OK) {
                  this.setState({
                    directions: result,
                  });
                } else {
                  console.error(`error fetching directions ${result}`);
                }
              });
            }
          })
        )(
        props =>
          <GoogleMap defaultZoom={15} defaultCenter={this.state.destinationPosition} >
            <Marker position={this.state.destinationPosition} draggable changeLat
                onDragEnd={this.onMarkerPositionChanged.bind(this)}
            />
            <Marker
                icon="https://www.robotwoods.com/dev/misc/bluecircle.png"
                position={this.state.currentPosition}
            />
            {this.state.direction && props.directions && <DirectionsRenderer directions={props.directions} />}
            <Button bsStyle="success" onClick={this.handleClick.bind(this)}>Current Position</Button>
            <Button bsStyle="success" onClick={this.changeDir.bind(this)}>Get Direction</Button>
          </GoogleMap>
        );
        return(
            <Container state={this.state} map={MyMapComponent}/>
        );
    }
}
export default map;

when i use constant numbers in place of origin and destination it works fine.


回答1:


First things first, change the name of the component map to Map. Have a read at this SO post.

Now, regarding this:

when i use constant numbers in place of origin and destination it works fine.

The answer to this question has a background, continue reading the following so that it is more clear.

The problem of the this context

In your code, you have a map component, and you can access any of its properties (including the state) with this. If you call this.state inside the render function for example, you will get the state of map. You can use the componentDidMount function of the map component, to, for example, set a value in the state, like this:

class map extends Component {

  constructor(props){
    this.state = { myVal: 1}
  }

  componentDidMount(){
    this.setState({ myVal: 2})
  }
}

This code will not fail, because componentDidMount is a React function for the component, and this can be used inside the component because it's in the same context. In this case, this.setState will set the state of map, because this is map.

This is the code of the example you provided:

const { compose, withProps, lifecycle } = require("recompose"); const {   withScriptjs,   withGoogleMap,   GoogleMap,   DirectionsRenderer, } = require("react-google-maps");

const MapWithADirectionsRenderer = compose(   withProps({
    googleMapURL: "https://maps.googleapis.com/maps/api/js?key=AIzaSyC4R6AN7SmujjPUIGKdyao2Kqitzr1kiRg&v=3.exp&libraries=geometry,drawing,places",
    loadingElement: <div style={{ height: `100%` }} />,
    containerElement: <div style={{ height: `400px` }} />,
    mapElement: <div style={{ height: `100%` }} />,   }),   withScriptjs,   withGoogleMap,   lifecycle({
    componentDidMount() {
      const DirectionsService = new google.maps.DirectionsService();

      DirectionsService.route({
        origin: new google.maps.LatLng(41.8507300, -87.6512600),
        destination: new google.maps.LatLng(41.8525800, -87.6514100),
        travelMode: google.maps.TravelMode.DRIVING,
      }, (result, status) => {
        if (status === google.maps.DirectionsStatus.OK) {
          this.setState({
            directions: result,
          });
        } else {
          console.error(`error fetching directions ${result}`);
        }
      });
    }   }) )(props =>   <GoogleMap
    defaultZoom={7}
    defaultCenter={new google.maps.LatLng(41.8507300, -87.6512600)}
  >
    {props.directions && <DirectionsRenderer directions={props.directions} />}   </GoogleMap> );

<MapWithADirectionsRenderer />

In this example, this.setState refers to the context of MapWithADirectionsRenderer, so this.setState is setting the state of MapWithADirectionsRenderer, because this is MapWithADirectionsRenderer.

Now, in your code you are creating a new component called MyMapComponent as a const object. This component lets you define the componentDidMount function of that new component. That new component will have it's own context, so when you write this inside the MyMapComponent component, it will refer to MyMapComponent, not to map.

You cannot access the state of map from within MyMapComponent like that, you have to pass props to MyMapComponent.

Solution

I created a couple of examples on how to pass values.

Solution 1

You can create a prop with any name (in this example it's called test with the value My value):

const MyMapComponent = compose(
  withProps({
    googleMapURL:
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyC5VMMlyr_A6K5ycpOrq3OsVM8YYbn0q3A&v=3.exp&libraries=geometry,drawing,places",
    loadingElement: <div style={{ height: `100%` }} />,
    containerElement: <div style={{ height: `400px` }} />,
    mapElement: <div style={{ height: `100%` }} />,
    test:'My value'
  }),
  lifecycle({
    componentDidMount() {
      console.log(JSON.stringify(this.props.test));
    }
  }),
  withScriptjs,
  withGoogleMap,
)(props => (
  <GoogleMap defaultZoom={8} defaultCenter={{ lat: -34.397, lng: 150.644 }}>
    {props.isMarkerShown && (
      <Marker position={{ lat: -34.397, lng: 150.644 }} />
    )}
  </GoogleMap>
));

This way, you can log this.props.test with the given value, whatever you use.

Solution 2

If what you need is to render the component depending on the value found in the state of Map, use something like this.

You can pass props to MyMapComponent an then access them using props. and adding the name of the prop.

For example if you pass a new prop called latProp like this:

<MyMapComponent isMarkerShown latProp={-34.397} />

You can access it like this:

(props => (
  <GoogleMap defaultZoom={8} defaultCenter={{ lat: -34.397, lng: 150.644 }}>
    {props.isMarkerShown && (
      <Marker position={{ lat: props.latProp, lng: 150.644 }} />
    )}
  </GoogleMap>
));

Since what you want is to replace the constant value with what you have in the state, just replace the values sent in props.latProp with whatever property you have in the state:

<MyMapComponent isMarkerShown latProp={this.state.lat} />

This is the complete component:

const MyMapComponent = compose(
  withProps({
    googleMapURL:
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyC5VMMlyr_A6K5ycpOrq3OsVM8YYbn0q3A&v=3.exp&libraries=geometry,drawing,places",
    loadingElement: <div style={{ height: `100%` }} />,
    containerElement: <div style={{ height: `400px` }} />,
    mapElement: <div style={{ height: `100%` }} />,
  }),
  withScriptjs,
  withGoogleMap,
)(props => (
  <GoogleMap defaultZoom={8} defaultCenter={{ lat: -34.397, lng: 150.644 }}>
    {props.isMarkerShown && (
      <Marker position={{ lat: props.latProp, lng: props.lngProp }} />
    )}
  </GoogleMap>
));

class App extends Component{
  constructor(props){
    super(props);
    this.state = { lat: -34.400, lng: 151.644 }
  }
  render(){
    return (
      <div>
        <MyMapComponent isMarkerShown latProp={this.state.lat} lngProp={this.state.lng} />
      </div>
    );
  }
}
export default App;


来源:https://stackoverflow.com/questions/53077608/cannot-access-state-in-componentdidmount

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!