fullstackreact / google-maps-react

Companion code to the "How to Write a Google Maps React Component" Tutorial
https://www.fullstackreact.com/articles/how-to-write-a-google-maps-react-component/
MIT License
1.64k stars 818 forks source link

Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node. #135

Open isabellachen opened 7 years ago

isabellachen commented 7 years ago

React throws this error when I delete a marker off the map. The usage is this: A marker component is placed on the map via new google.maps.Marker({...}), when it is clicked again, its latlng is sent to the reducer, which removes the marker from the Redux store. The marker is removed, but when that happens, I get the error. I read this comment on the React github page which says the error happens when "you’re trying to do DOM mutations by hand because it later tries to reconcile the DOM, and repeats what you’ve already done, causing failures." I not sure what's going on, but I think google-maps-react is trying to do this.

My code - App.js

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {connect} from 'react-redux';
import {GoogleApiWrapper} from 'google-maps-react';

import GoogleMap from './GoogleMap';
import Marker from './Marker';
import './styles.scss'

class App extends Component {
  constructor (props) {
    super(props);
    this.state = {};
  }

  addMarker = () => {
    const desc = document.getElementById('desc').value;
    chrome.tabs.getSelected(null, tab => {
      this.props.addMarker({
        url: tab.url,
        title: tab.title,
        desc: desc,
        place: this.state.place,
        latLng: this.state.latLng,
        date: this.state.date,
      });
    });
  };

  //passed down and called from Marker child component
  deleteMarker = (marker) => {
    marker.center = {
      lat: marker.position.lat(),
      lng: marker.position.lng(),
    };
    //set prop latLng as stringified version of the center obj
    marker.latLng = JSON.stringify(marker.center);
    this.props.deleteMarker(marker);
  }

render () {

    if (!this.props.loaded) {
      return <div>Loading...</div>;
    }

    return (
      <div className="app">

        <GoogleMap ref="map"
          google={this.props.google}
          markers={this.props.markers}
          placeMarker={this.placeMarker}
          deleteMarker={this.deleteMarker}
          place={this.state.place}
          latLng={this.state.latLng}
        />

        <input id="findCenter"
          className="find-center"
          type="text"
          ref="findCenter"
          onKeyPress={this.findCenter}
          placeholder="Search Google Maps"
        />

        <textarea id="desc" className="desc" cols="40" rows="5"/>

        <button onClick={this.addMarker}>+</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => ({
  markers: state.markers,
});

const mapDispatchToProps = (dispatch) => ({
  addMarker: (marker) => dispatch({
    type: 'ADD_MARKER',
    url: marker.url,
    title: marker.title,
    desc: marker.desc,
    place: marker.place,
    latLng: marker.latLng,
    date: marker.date,
  }),

  deleteMarker: (marker) => dispatch({
    type: 'DELETE_MARKER',
    latLng: marker.latLng,
  }),
});

const connectAppToRedux = connect(mapStateToProps, mapDispatchToProps)(App);

export default GoogleApiWrapper({
  apiKey: 'AIzaSyC6xXldmd60eN7osRK0BPQjoCsMKYo0eiI',
})(connectAppToRedux);

GoogleMap.js - child of App

class GoogleMap extends Component {

constructor (props) {
    super(props);

    const {lat, lng} = this.props.initialCenter;
    this.state = {
      currentCenter: {
        lat: lat,
        lng: lng,
      },
    };
  }

  componentDidUpdate (prevProps, prevState) {
    //check if props has been updated when app is first loaded
    if (prevProps.google !== this.props.google) {
      this.loadMap();
    }
    //check it a new marker has been added to redux store and passed down
    if (prevProps.markers !== this.props.markers) {
      this.getLatestMarker();
    }
    //check if state change when placing temp marker
    if (prevState !== this.state) {
      this.loadMap();
    }
  }

  componentWillReceiveProps (nextProps) {
    //when DELETE_MARKER is dispatched, re-load the map
    if (this.props.markers !== nextProps.markers) {
      this.loadMap();
    }
  }

  loadMap () {
    if (this.props && this.props.google) {
      //if the google api has loaded into props
      const {google} = this.props;
      const maps = google.maps;

      //reference to GoogleMap's div node
      const mapRef = this.refs.map;
      const node = ReactDOM.findDOMNode(mapRef);

      let zoom = this.props.zoom; //zoom set via default props
      //currentCenter set to default props initialCenter in state
      const {lat, lng} = this.state.currentCenter;
      const center = {lat, lng};
      const mapConfig = {
        center: center,
        zoom: zoom,
        styles: GoogleMapStyles,
      };
      this.map = new maps.Map(node, mapConfig);

      //add listener for clicks on map to place markers
      this.map.addListener('click', (e) => {
        this.setTempMarker(e.latLng);
      });
    }
  }

  renderMarkers () {
    if (this.props.markers) {
      const markers = this.props.markers;
      return Object.keys(markers).map(marker => {
        return <Marker
          key={marker}
          google={this.props.google}
          marker={markers[marker]}
          map={this.map}
          deleteMarker={this.props.deleteMarker}
        />;
      });
    }
  }

  render () {
    return (
      <div ref="map" className="map">
        {this.renderMarkers()}
      </div>
    );
  }
}

Marker.js - child of GoogleMap

class Marker extends Component {
  constructor (props) {
    super(props);
  }

  componentDidMount () {
    this.renderMarker();
  }

  componentDidUpdate () {
    this.renderMarker();
  }

  renderMarker () {
    const google = this.props.google;
    const map = this.props.map;
    const markerInfo = this.props.marker;
    const marker = new google.maps.Marker({
      position: markerInfo.latLng,
      map: map,
    });
    marker.addListener('click', () => {
      this.props.deleteMarker(marker);
    });
  }

  render () {
    return null;
  }
}
auser commented 7 years ago

I'll check this out and see if I can replicate this error.

isabellachen commented 6 years ago

I removed the marker component and used the Google Maps API to manipulate the marker directly and the error went away :)