Where shoud I call Google map methods in my react app?

Viewed 149

I'm using redux to manage my app state,and using Google map(or other map) to show my data.

I have a very complex UI and want to manage it with redux. But I have a question below:

  1. I have two components A and B, they are in separate parts, A is a button , B is a component with Google map in it as a inner property called "map"
  2. When Click A ,I want to trigger map.panBy(-10,-20), the result is that center will be changed, but by executing panBy there is an animation, in this case (-10, -20) is not the result, it's just offset, so save it(offset) on the store is not a good idea. What shoud I do?
  3. Solution I: using redux data flow, dispatch an action like {type:'panBy', payload: [-10, -20]}, and when component B get the data, execute map.panBy(-10,-20). --> -10, -20 are temporary params, store them is weird.
  4. Solution II : Eventbus, component A trigger event and component B subscribe it .
  5. Solution III: Expose map to it's parent or to the window object. --> weird too...

So, is there a common solution to resolve this?

Can I use redux middleware? But middleware DONOT know the map either.

2 Answers

If I'm understanding you correctly:

  1. You have some data
  2. You are going to display that data on a map.

What's a little unclear with your question - is whether the panBy parameters are data you are storing in your redux state.

It's a little bit odd that you would store those parameters in the redux state, because those are more actions that you do once, rather than objective data about a thing.

What I mean is - the kind of data I imagine you would store in state would be data like 'name', 'address', 'lat lng', data that is always going to look the same when you apply it. Whereas 'pan' data is going to be displayed differently each time you display it.

But in any case - if you are just displaying/applying whatever you have in your redux state - what I would do is:

  1. Connect the redux state to the component via mapStateToProps
  2. Implement componentDidUpdate, and if the mapData has updated, then apply all the relevant map methods.

But back to the panBy thing - I would suggest that instead of storing panBy parameters, you should be storing the map center and zoom level. That way, it will look the same everytime you apply it.

Alternatively, you could store the original center and the panBy parameters, and use that recalc the map each time.

ie. the redux state would like:

mapState: {

    center: {
     x: 100, 
     y: 200, 
    },
    panBy: {
     x: 10, 
     y: 0
    }    
}

and then in your componentDidUpdate you both recenter it - and apply the pan, something like:

componentDidUpdate(prevProps) {
    if (this.props.mapState !== prevProps.mapState) {
         const {mapState} = this.props.mapState; 
         Map.center(mapState.center); 
         Map.panBy(mapState.panBy); 
    }
}

The disadvantage with this - is that you are redundantly recentering the map each time, and depending on how the API works, it might look bad.

I do think the better solution, is to manually recalculate the center yourself - as Spark.Bao suggests.

agree with @dwjohnston, you should save the state likes center and zoom in the redux store, your map should have an initial center at least, right, that value should be your initial state, for example:

const initialState = {
  center: {
    lat: 100,
    lng: 100
  },
  zoom: ...
}

function mapOptions(state = initialState, action) {
  switch(action.type) {
    ...
  }
}

after dispatching the action, the reducer should modify the state according to the payload (the action should be {type: "panBy", payload: [-10, -20]}):

function mapOptions(state = initialState, action) {
  switch(action.type) {
    case "panBy":
      const offset = action.payload
      return {
        ...state,
        center: {
          lat: state.center.lat + offset[0],
          lng: state.center.lng + offset[1]
        }
      } 
  }
}

then your component B should update the map center by the new state.


don't know how far the A component and B component separate, if A component is inside B component, you call use callback from A until it reaches B, for example:

class A extends React.Component {
  render() {
    <button onClick={()=>this.props.moveMap(-10, -20)}>Move</button>
  }
}

class B extends React.Component {
  componentDidMount() {
    this.map = new google.maps.Map(document.getElementById('map'), {
      center: {lat: -34.397, lng: 150.644},
      zoom: 8
    });
  }

  handleMoveMap = (offsetLng, offsetLat) => {
    this.map.panBy(offsetLng, offsetLat)
  }

  render() {
    <div>
      <div id="map"/>
      <A moveMap={this.handleMoveMap}/>
    </div>
  }
}

else you can choose Solution III, it works and more convenient, but it is not React pattern (data drive UI), it is jQuery pattern (manually operate DOM). (sometimes I use jQuery pattern in my React project as well).

in React and Redux thinking, you should put the shared data by different components in the redux store.

Related