Passing props in child component from state of parent component - reactjs

I am unable to use the state from Parent Component to child component on Google Maps component. This may sound a very basic question, however I am trying to understand the difference between one of props working and other not.
So I have a google maps component (import GoogleMapReact from 'google-map-react')
static defaultProps = {
center: { lat: 62.10281, lng: -84.14565 },
zoom: 11
};
constructor(props) {
console.log(props);
super(props);
}
render() {
return (
<div className='google-map' style={{ height: '100%', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: 'somekey' }}
center={this.props.center}
defaultZoom={ this.props.zoom }>
<AnyReactComponent
lat={ this.props.lat }
lng={ this.props.lng }
text={ 'Wheres Waldo?' }
/>
</GoogleMapReact>
</div>
)
}
and then from parent component
<MapComponent lat={this.state.latitude} lng={this.state.longitude}
center={{lat: this.state.latitude, lng: this.state.longitude}}
I have checked the value to lat, lng and center in constructor and I am getting them as expected still map doesn't work
However, If I hardcode the value in center leaving the lat and lng getting value from state, then it does work.
Now this works, Can someone please explain this concept and the difference and how to overcome it? Your help is appreciated.
<MapComponent lat={this.state.latitude} lng={this.state.longitude}
center={{lat: 62.10281, lng: -84.14565}}

Related

react google maps - Move data between functions

I have the example code of react-google-maps and it works great.
I'm now trying to create a page where the user can influence what is displayed on the map (some kind of filtering).
I have 2 functions that need to talk to each other, the code built this way:
import {
withGoogleMap,
withScriptjs,
GoogleMap,
Marker,
InfoWindow
} from "react-google-maps"
function Map() {
useEffect(() => {
// Getting the data
}
return (
<GoogleMap
zoom={userLocation.zoom}
center={{ lat: userLocation.lat, lng: userLocation.lng }}
>
</GoogleMap>
)
Now that's where things get a little complicated:
const MapWrapped = withScriptjs(withGoogleMap(Map))
export default function App() {
return (
// user interface for filtering goes here!
<MapWrapped
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=${''}`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
)
}
I need to get data (useState) from the function Map to function App.
I can't find a way to do so.
I can't declare use state outside a function and if I put everything in one function the map just keeps reloading.
Just needed a good night sleep:
Changed the map function to look like that:
const Map = ((props) => {
And now I'm able to get props from the App function.
Will declare the useStates on the App function and move the data that way

react-google-maps map not updated when passed props and the marker is one location behind when updated

I have the map working when it loads, but whenever I pass props the map itself never changes.
The marker changes, but is always 1 state behind what it should be, obviously a sync issue but I'm not sure how to fix this in my component.
class Map extends React.Component {
state = {
center: { lat: 50.937531, lng: 6.960278600000038 }
}
componentWillReceiveProps = () => {
this.setState({
center: {
lat: Number(parseFloat(this.props.city.lat)),
lng: Number(parseFloat(this.props.city.lng))
}
});
}
render() {
console.log(this.state.center)
return (
<div>
<MapDetails
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyB-L-IikkM6BZ_3Z2QIzbkx4pdAkP76tok&v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100vh`}} />}
mapElement={<div style={{ height: `100%` }} />}
mapCenter={this.state.center}
/>
</div>
)
}
}
const mapStateToProps = (state) => {
return { city: state.CityNameReducer }
}
export default connect(mapStateToProps)(Map);
const MapDetails = withScriptjs(withGoogleMap(props =>
<GoogleMap
defaultZoom={12}
defaultCenter={props.mapCenter}
>
<Marker
position={props.mapCenter}
/>
</GoogleMap>
));
The biggest question I have is why the map itself is not updating?
Add a key to GoogleMap component. a Key should be unique each time provided. for your testing use new Date().getTime() function.
<GoogleMap key={new Date().getTime()}/>
as I mentioned its for only testing so make sure to provide this key in better way
I may recommend you to use shouldComponentUpdate(nextProps, nextState) {} instead of componentWillReceiveProps().
I recommend you to read the following page, to understand how it works.
Updating and componentWillReceiveProps
I don't have more information about what is happening in the parent of your component, so I cannot go deeper to the problem. But I think you can get the solution by changing that.

Google Maps React, adding markers with lat lng

Im using "google-maps-react" and trying to add new markers to my map with clicks.
I currently manage to console log the specific latLng, but cant seem to make a new one. I'm pretty new to React.
My onMapClick works with finding the latitude and longitude. But I think I need to add the position to an array and then use that one to update the map. Might be wrong
onMapClick = (map,maps,e) => {
const { latLng } = e;
const latitude = e.latLng.lat();
const longitude = e.latLng.lng();
console.log(latitude + ", " + longitude);
var marker = new window.google.maps.Marker({
position: e.latLng,
setMap: map,
});
}
The solution Im currently on is that I just hardcoded some Markers in my render() with the location of array in Marker
<Marker
onClick={this.onMarkerClick}
name={storedLocations[0]}
position={{lat:listLatitude[0], lan:listLongitude[0]}}
/>
My InfoWindow is:
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={this.onClose}
>
<div>
<h4>{this.state.selectedPlace.name}</h4>
</div>
</InfoWindow>
</Map>
My onMapClick works with finding the latitude and longitude. But I
think I need to add the position to an array and then use that one to
update the map.
That's indeed would the React way but instead of instantiating markers via Google Maps API, consider to keep the data (marker locations) via state and React will do the rest like this:
class MapContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
locations: []
};
this.handleMapClick = this.handleMapClick.bind(this);
}
handleMapClick = (ref, map, ev) => {
const location = ev.latLng;
this.setState(prevState => ({
locations: [...prevState.locations, location]
}));
map.panTo(location);
};
render() {
return (
<div className="map-container">
<Map
google={this.props.google}
className={"map"}
zoom={this.props.zoom}
initialCenter={this.props.center}
onClick={this.handleMapClick}
>
{this.state.locations.map((location, i) => {
return (
<Marker
key={i}
position={{ lat: location.lat(), lng: location.lng() }}
/>
);
})}
</Map>
</div>
);
}
}
Explanation:
locations state is used to track all the locations once the map is clicked
locations state is passed to Marker component to render markers
The next step could be to introduce a separate component for markers list, Thinking in React tells about components the follows:
One such technique is the single responsibility principle, that is, a
component should ideally only do one thing. If it ends up growing, it
should be decomposed into smaller subcomponents.
const MarkersList = props => {
const { locations, ...markerProps } = props;
return (
<span>
{locations.map((location, i) => {
return (
<Marker
key={i}
{...markerProps}
position={{ lat: location.lat(), lng: location.lng() }}
/>
);
})}
</span>
);
};
Here is a demo which demonstrates how to add a makers on map click via google-maps-react library

How can animation be added to markers in "react-google-maps"?

The docs - https://github.com/tomchentw/react-google-maps/tree/v5.1.0#documentation - say that "animation" can be added to markers. However, I cannot seem to figure out how to add the animation. There are also other properties I tried to add, but I was not able to. Code snippet is posted below:
const ProvidenceMap = withGoogleMap(props => (
<GoogleMap
defaultZoom={13}
defaultCenter={{ lat: 41.8240, lng: -71.4128 }}
>
{props.markers.map((marker, index) => (
<Marker
key={index}
position={marker.position}
onClick={() => props.onMarkerClick(marker)}
animation={marker.animation}
>
{marker.showInfo && (
<InfoWindow onCloseClick={() => props.onMarkerClose(marker)}>
{
<div className="info-window">this is the info window</div>
}
</InfoWindow>
)}
</Marker>
))};
</GoogleMap>
));
class ContentContainer extends React.Component {
constructor() {
super();
this.state = {
landing: true,
map: false,
wishListTitle: "",
markers: [
{
position: {lat: 41.8240, lng:-71.4128},
showInfo: false,
animation: "DROP",
},
{
position: {lat: 41.8250, lng: -71.4338},
showInfo: false,
animation: "DROP",
}
],
}
}
/* click handlers and render() have been omitted for simplicity */
}
The part of the README that mentions the animation prop refers you to the official documentation for the google.maps.Marker class. That documentation gives the method signature for setAnimation as:
setAnimation(animation:Animation)
...which tells you that the type of the animation argument—and, presumably, the React animation prop—should be Animation. If you click on the link, it takes you to Animation constants, where you learn that two constants are defined on google.maps.Animation: BOUNCE and DROP.
That tells you that the value for this prop should be either google.maps.Animation.BOUNCE or google.maps.Animation.DROP. And so:
[
{
position: {lat: 41.8240, lng:-71.4128},
showInfo: false,
animation: google.maps.Animation.DROP,
},
{
position: {lat: 41.8250, lng: -71.4338},
showInfo: false,
animation: google.maps.Animation.DROP,
}
],
This matches what you can see in the react-google-maps examples, where other google.maps.* constants are used.
When using the 'google-maps-react' component from NPM, I was able to enable animations by adding this.props before google.maps.Animation.DROP on the marker tag. Specifically:
<Marker
animation= {this.props.google.maps.Animation.DROP}
/>
Adding animation on the <Marker /> is easy. Just add defaultAnimation props on your <Marker />s like this:
<Marker
...
defaultAnimation={google.maps.Animation.DROP}
>
</Marker>
and it should work just fine.

MobX React: How to only Re-render part of View

I have a map react component and I'm dynamically adding markers.The problem is that when I add markers in the store, the whole map rerenders instead of just appending the markers to the map. Has anyone got any suggestions on how this could be fixed?? I'm pretty sure that I need to Inject the store into CPMap function exclusively, I'm just not sure how.
const CPMap = withGoogleMap((props) => (
<GoogleMap
ref={props.onMapLoad}
style={{
height: 100,
width: 100,
}}
onCenterChanged={props.boundsChanged}
defaultOptions={{ styles: this.mapStyles }}
defaultZoom={props.zoom}
defaultCenter={{ lat: props.center.lat, lng: props.center.lng }}>
<MarkerClusterer
gridSize={40}>
{
props.markers.map(({ key, position }) => (
<Marker
key={key}
position={{ lat: position.lat, lng: position.lng }}
icon={{
url: require('../../images/marker.png')
}}
/>
))
}
</MarkerClusterer>
</GoogleMap >
))
return (
<CPMap
style={{
height: 100,
width: 100,
}}
onMapLoad={(gMap) => {
map = gMap
this.props.map.fetchMarkers()
}}
boundsChanged={() => {
this.props.map.fetchMarkers()
}}
center={this.props.map.center}
zoom={this.props.map.zoom}
markers={mobx.toJS(this.props.map.markers)}
containerElement={
<div style={{ height: 'calc(100vh - 70px)' }
} />
}
mapElement={
<div style={{ height: 'calc(100vh - 70px)' }} />
} />
)
}
I'd propose you the following solution: don't pass makers directly to CMap component, but use store instead;
const markersStore = observable({
markers: []
});
Then in your component - move MarkersClusterer to the separate component and pass markersStore one level deeper:
//import markersStore or inject via mobx-react package
class MyComponent extends Component {
render() {
const CMap = withGoogleMap((props) => (
<GoogleMap
...
<Clusterer markersStore={props.markersStore} />
/>
))
return (
<CMap
...
markersStore={markersStore}
/>
)
}
}
Make your new Cluster component observable so it updates when markersStore "markers" property updates;
#observable
class Clusterer extends Component {
render(){
const { markers } = this.props.markersStore;
return (
<MarkerClusterer>
{ markers.map(...)}
</MarkerClusterer>
)
}
}
The final change is to update your fetchMarkers method so it populates the markersStore with new data.
With this solution CMap component doesn't know anything about markers and it doesn't updates when new data received. At the same time MarkersClusterer component becomes more clever and "observes" changes in the markersStore.

Resources