4

I am teaching myself React whilst working on a project which uses the react-google-maps package to produce a map with directions from A to B. The map itself works fine, but I've now tried to print the corresponding route directions in html via the return method but cannot get it these instructions to print out.

From my research via Google and StackOverflow I think my issue may either be:

  1. The scope of the 'this' keyword when trying to access my instructionList in the return method. In which case - what would I need to type to access my instructionList array of <li> items? I've also tried <ol>{DirectionsService.route.state.instructionList}</ol> and <ol> and {DirectionsComponent.DirectionsService.route.state.instructionList}</ol> which also didn't work

  2. That when the page is loaded, the api response hasn't necessarily been received and thus my instructionList is null and cannot be rendered. In which case - how should this be handled?

  3. Something else I'm unaware of in my syntax (I'm very much a beginner to react, and the react-google-maps package!)

In my code, I've defined an array called instructionList in the state which contains instructions for getting from A to B

if (status === google.maps.DirectionsStatus.OK) {
  this.setState({
    directions: { ...result },
    markers: true
  });
  this.setState({
    instructions: this.state.directions.routes[0].legs[0].steps
  });
  this.setState({
    instructionList: this.state.instructions.map(instruction => {
      return <li>instruction.instructions</li>;
    })
  });
}

I'm then trying to access this array in the class return method - but the error message is saying that instructionList is not defined.

return (
  <div>
      <DirectionsComponent/>
      <div className="route instructions">
        <h1>{title}</h1>
        <ol>{this.state.instructionList}</ol>
      </div>
      <NotificationContainer />
  </div>

Below is a fuller piece of code if that makes it easier to identify the issue.

class MyMapComponent extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const {
      startLat,
      startLng,
      finishLat,
      finishLng,
      transportMode,
      title
    } = this.props;

    const DirectionsComponent = compose(
      withProps({
        googleMapURL:
          "https://maps.googleapis.com/maps/api/js?key=APIKEYGOESHERE", //removed=&callback=initMap
        loadingElement: <div style={{ height: `400px` }} />,
        containerElement: <div style={{ width: `100%` }} />,
        mapElement: <div style={{ height: `400px`, width: `400px` }} />
      }),
      withScriptjs,
      withGoogleMap,
      lifecycle({
        componentDidMount() {
          const DirectionsService = new google.maps.DirectionsService();

          DirectionsService.route(
            {
              origin: new google.maps.LatLng(startLat, startLng),
              destination: new google.maps.LatLng(finishLat, finishLng),
              travelMode: google.maps.TravelMode[transportMode],
              provideRouteAlternatives: true
            },
            (result, status) => {
              if (status === google.maps.DirectionsStatus.OK) {
                this.setState({
                  directions: { ...result },
                  markers: true
                });
                this.setState({
                  instructions: this.state.directions.routes[0].legs[0].steps
                });
                this.setState({
                  instructionList: this.state.instructions.map(instruction => {
                    return <li>instruction.instructions</li>;
                  })
                });
              } else {
                console.error(
                  `There was an error fetching directions for the specified journey ${result}`
                );
                NotificationManager.error(
                  "Journey cannot be retrieved, please try again",
                  "Error",
                  20000
                );
              }
            }
          );
        }
      })
    )(props => (
      <GoogleMap defaultZoom={3}>
        {props.directions && (
          <DirectionsRenderer
            directions={props.directions}
            suppressMarkers={props.markers}
          />
        )}
      </GoogleMap>
    ));
    return (
      <div>
        <DirectionsComponent />
        <div className="route instructions">
          <h1>{title}</h1>
          <ol>{this.state.instructionList}</ol>
        </div>
        <NotificationContainer />
      </div>
    );
  }
}
export default MyMapComponent;

Error message is currently TypeError: Cannot read property 'instructionList' of null

I have played around with the code and researched quite a bit but I'm going round in circles. I'm sure the solution is a quick one but I'm struggling to find it with my limited knowledge of React/react-google-maps so I'm very appreciative of anyone who is able to help :)

dance2die
  • 35,807
  • 39
  • 131
  • 194
Faith
  • 101
  • 1
  • 8

1 Answers1

1

You haven't init your component's state. So you can't access a property of state. You need to init it in constructor.

 constructor(props){
    super(props);
    this.state = { instructionList: [] };
  }

Updated

You need to define onChangeInstructionList to change MyMapComponent's instructionList inside DirectionsComponent. You also need to move DirectionsComponent to componentDidMount of MyMapComponent to avoid infinite loop because of state changes.

class MyMapComponent {
  constructor(props){
    super(props);
    this.state = {
      instructionList: [],
    };

    this.onChangeInstructionList = this.onChangeInstructionList.bind(this);
  }

  componentDidMount() {
    const {startLat, startLng, finishLat, finishLng, transportMode} = this.props;
    const DirectionsComponent = compose(
      withProps({
        googleMapURL: "https://maps.googleapis.com/maps/api/js?key=APIKEYGOESHERE",//removed=&callback=initMap
        loadingElement: <div style={{ height: `400px` }} />,
        containerElement: <div style={{ width: `100%` }} />,
        mapElement: <div style={{height: `400px`, width: `400px` }}  />,
        onChangeInstructionList: this.onChangeInstructionList,
      }),
      withScriptjs,
      withGoogleMap,
      lifecycle({
        componentDidMount() {
          const DirectionsService = new google.maps.DirectionsService();

          DirectionsService.route({
            origin: new google.maps.LatLng(startLat, startLng),
            destination: new google.maps.LatLng(finishLat, finishLng),
            travelMode: google.maps.TravelMode[transportMode],
            provideRouteAlternatives: true
          }, (result, status) => {

            if (status === google.maps.DirectionsStatus.OK) {
              this.setState({
                directions: {...result},
                markers: true
              })
              this.setState({instructions: this.state.directions.routes[0].legs[0].steps});
              this.props.onChangeInstructionList(this.state.instructions.map(instruction => {
                return (<li>instruction.instructions</li>);
              }));
            } else {
              console.error(`There was an error fetching directions for the specified journey ${result}`);
              NotificationManager.error("Journey cannot be retrieved, please try again", "Error", 20000);
            }
          });
        }
      })
    )(props =>
      <GoogleMap
        defaultZoom={3}
      >
        {props.directions && <DirectionsRenderer directions={props.directions} suppressMarkers={props.markers}/>}
      </GoogleMap>
    );

    this.setState({
      DirectionsComponent,
    })
  }

  onChangeInstructionList(newList) {
    this.setState({
      instructionList: newList,
    });
  }

  render() {
    const {title} = this.props;
    const { DirectionsComponent, instructionList } = this.state;
    return (
      <div>
        <DirectionsComponent/>
        <div className="route instructions">
          <h1>{title}</h1>
          <ol>{instructionList}</ol>
        </div>
        <NotificationContainer />
      </div>

    )
  }
}
export default MyMapComponent
Tien Duong
  • 2,517
  • 1
  • 10
  • 27
  • Thanks - this has stopped the error message - but my instruction list still isn't printing as I'm expecting it too. I'm wondering whether `this.state.instructionList` is the wrong thing to be calling in the return method? – Faith Aug 04 '19 at 00:43
  • ```this.state.instructionList``` you called in ```render``` is ```instructionList``` of ```MyMapComponent```. But you put your data in ```instructionList``` of ```DirectionsComponent ```. That's the reason you get empty list in your render – Tien Duong Aug 04 '19 at 00:49
  • Thank you - I understand what you are saying, and I thought this might be what is happening.However I don't know the correct syntax that I need instead to access the state from the DirectionsComponent instead. I tried `DirectionsService.route.state.instructionList` but that didn't work. Could you kindly give me any guidance with this? Sorry if this is a very idiotic thing to ask-I am very grateful for your help! – Faith Aug 04 '19 at 01:11