MicheleBertoli / react-gmaps

A Google Maps component for React.js

Home Page:http://react-gmaps.herokuapp.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Map re-centers on re-render

mebrunet opened this issue · comments

When I change the lat / lng values of one of the Markers or Circles in the map, the map seems to re-center itself to its original position (i.e. to its lat, lng props). How can this be avoided? I don't want the current view of the map to change when I dynamically move Markers or Circles around.

Hello @mebrunet, that shouldn't happen.
Can you please provide an example to reproduce the issue?
Thanks!

Sorry @MicheleBertoli, was swamped these last weeks. The problem came up specifically when Markers, Circles, or Info-Windows moved locations on re-render. Perhaps I should have been more specific.

In the example below, the map re-centers every 3 seconds. I saw no clear way around this so I just pulled react-google-maps into the project instead.

import React, { Component } from 'react';
import {Gmaps, Marker, InfoWindow, Circle} from 'react-gmaps';

const coords = {
  lat: 51,
  lng: 0
};

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      lat: 51,
      lng: 0
    }
    this.interval = setInterval(() => {
      this.setState({
        lat: 51 + Math.random(),
        lng: 0 + Math.random(),
      })
    }, 3000);
  }

  render() {
    const { lat, lng } = this.state;

    return (
      <div className="App">
        <Gmaps
          width={'800px'}
          height={'600px'}
          lat={coords.lat}
          lng={coords.lng}
          zoom={12}
          loadingMessage={'Be happy'}
          params={{v: '3.exp'}}
          onMapCreated={this.onMapCreated}>
          <Marker
            lat={lat}
            lng={lng}
            draggable={true}
            onDragEnd={this.onDragEnd} />
          <InfoWindow
            lat={coords.lat}
            lng={coords.lng}
            content={'Original Center'}
            onCloseClick={this.onCloseClick} />
          <Circle
            lat={coords.lat}
            lng={coords.lng}
            radius={500}
            onClick={this.onClick} />
        </Gmaps>
      </div>
    );
  }
}

export default App;

Hey @mebrunet.
Luckily, the solution is easy and the cause is not the package.
It's more about the way JavaScript works.

The Gmaps component "refreshes" because you are passing different props to it every 3 secs.
In fact, even if {v: '3.exp'} never changes, a new object gets created on every render.

If you just define a const params = {v: '3.exp'} outside the render and you use it to set the component's prop like params={params}, the map will behave as expected.

@MicheleBertoli - thanks for identifying the source!

IMHO, this is still an issue the package can and should address. I think that if a library's role is to manage an object with a state, and be the user's only interface to managing that object, then it should be doing everything in its power to protect against unintentional changes in state / re-instantiation.

Knowing that some of the Gmap's props are objects, why not do a deeper object comparison in utils/compare-props, possibly pulling in a package like Immutable to help?

I quickly patched utils/compare-props in a way that solves the problem.

export default (props, nextProps) => {

  const propsKeys = Object.keys(props);
  const nextPropsKeys = Object.keys(nextProps);
  if (propsKeys.length !== nextPropsKeys.length) {
    return false;
  }

  for (let i = 0; i < propsKeys.length; i++) {
    const key = propsKeys[i];
    if (key === 'params' && props.params && nextProps.params) {
      const paramKeys = Object.keys(props.params);
      const nextParamKeys = Object.keys(nextProps.params);
      if(paramKeys.length !== nextParamKeys.length) {
        return false;
      }

      for(let j = 0; j < paramKeys.length; j++) {
        const paramKey = nextParamKeys[j];
        if(!nextProps.params.hasOwnProperty(paramKey) || 
          props.params[paramKey] !== nextProps.params[paramKey]) {
          return false;
        }
      }
    } else if ((key !== 'children' && key.indexOf('on') !== 0) &&
      (!nextProps.hasOwnProperty(key) || props[key] !== nextProps[key])) {
        return false;
    }
  }

  return true;

};

With these modifications, the map does not recenter under my above example use.

Curious to hear you thoughts.

Thanks for taking time to work on this @mebrunet.
However, I don't believe the library should make any assumption on the way devs are using it.

Deep comparison is expensive, and the PureComponent is behaving in the same way.
Creating constant objects inside the render is an anti-pattern and it should be avoided in general.

Fair enough... no reason to babysit people like me ;)

Maybe it would be worth adding a note to the readme though? Even when props are flat, I don't expect a change in their value to reset the component's state.

Also, perhaps not coding the anti-pattern into the landing page example if it's known to cause problems with this component?

I don't think is about "babysitting".
Even if the library would hide the problem in this scenario, creating const in that way could potentially harm the perf or your application (if you are using pure components) and therefore it should be avoided.

it's known to cause problems with this component

In fact, it doesn't cause problems only to this component but to any component that re-render when the props change.

In the readme example it doesn't create any problem because the component does not re-render but I'll update it to avoid any misunderstanding. Thanks!

@MicheleBertoli - I realize my last comment may have sounded sassy. Apologies, not my intent. Was simply agreeing that it was unnecessary (and you're right -- probably harmful) to try and build in "catches" when the problem was elsewhere. Also just identifying where I tripped up using the lib.

it doesn't cause problems only to this component but to any component that re-render when the props change

From a performance standpoint you're prob right, but I've never seen it affect behaviour / reset state. For example using an MUI component like:

<TextField style={{ color: 'black' }} />

doesn't cause the input text to disappear every time the component re-renders. Hence the "known to cause problems" remark.

Absolutely clear, thanks for your comment :)