react-google-maps pass props on marker click - reactjs

I am trying to integrate "react-google-maps" in my app to display a map. I am having the hardest time understanding their system for markers.
https://tomchentw.github.io/react-google-maps/#marker
I am able to display my map and get all of my markers displaying correctly. The next step is where I am having problems. I need to be able to click on each marker and know know what marker is being clicked on. I have commented the following code to show what props I am trying to show in my console for now. I think that I just need to pass another argument in the event but I have no idea how to do that. Truthfully right now I believe I am passing the event listener but I not even fully sure what I am looking at when I log that 'e' variable.
You can see what I have so far in my github. If you click on the marker you will see what I am logging.
https://joweber123.github.io/take-me-there/login/James
Please help, I can't find this information discussed anywhere else. Thank you so so so much
import React, { Component } from 'react';
import { withGoogleMap, GoogleMap, Marker, } from "react-google-maps";
import { compose, withProps } from "recompose";
class List extends Component {
//I eventually will use this information to set state and all of that, but for now I just don't understand how to pass information to this function from my marker on onClick
handleMarkerClick(e){
console.log(e);
}
render(){
const { compose, lifecycle} = require("recompose");
const {
withGoogleMap,
GoogleMap,
Marker,
} = require("react-google-maps");
const MapWithAMarker = compose(
withGoogleMap
)(props =>
<GoogleMap
defaultZoom={8}
defaultCenter={{ lat: Number(`${this.props.locations[Object.keys(this.props.locations)[Object.keys(this.props.locations).length-1]].location.lat}`), lng: Number(`${this.props.locations[Object.keys(this.props.locations)[Object.keys(this.props.locations).length-1]].location.lng}`) }}
locations={this.props.locations}
>
{
Object
.keys(this.props.locations)
.map(key =>
<Marker
key={key}
position={{ lat: Number(`${this.props.locations[key].location.lat}`), lng: Number(`${this.props.locations[key].location.lng}`) }}
locations={this.props.locations[key]}
//I want to be able to pass the information that is stored in my locations prop here, but I have no idea how to do that.
onClick={props.onMarkerClick}
/>
)
}
</GoogleMap>
);
return (
<div className = "location-list one-third column center border-main full-height">
<MapWithAMarker
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
locations={this.props.locations}
//I am not even really sure what I am doing here. What gets printed out in my console gives me some information but what I really want to be able to do is to pass the locations props of my specific marker and have access to that information
onMarkerClick={(e)=>this.handleMarkerClick(e)}
/>
</div>
);
}
}
export default List;

I was able to figure it out! I was just passing the event listener before so of course it didn't contain any of the information that I needed. I also didn't need to pass it its parent as just having it trigger its own event was enough. Thanks everyone for having a look at this.
onClick={()=> this.props.handleMarkerClick(this.props.locations[key])}
I guess I wasn't asking myself the right question but once I found these two answers I understood much better what I needed to do.
React - Passing props to child onClick
Pass props to parent component in React.js

Can you show what is contained in this.props.locations? Usually you want to map the locations and each location should have some kind of id field. Instead of mapping through the keys you should map through the array of locations. That way you can pass the id (or even the coordinates if they are in the location object itself, which I presume they do) when the onClick function is called.

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

Fit a map's bounds to contents of a FeatureGroup in React-Leaflet

I have a FeatureGroup with a Circle inside of it. On load, I want the map's viewport to completely show the circle. There are a number of relevant StackOverflow questions (this one, another one, a third), and I have tried them all. None have worked for me using modern tools.
import { MapContainer, TileLayer, FeatureGroup, Circle } from 'react-leaflet'
export default function MyMap({volunteer, pollingPlaces}) {
const coordinates = [40.781753, -73.966583];
const circle = <Circle pathOptions={{color: 'purple'}} center={coordinates} radius={3*1609} />
return (
<MapContainer center={coordinates}
style={{
height:"400px",
width: "400px"
}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<FeatureGroup>
{circle}
</FeatureGroup>
</MapContainer>
)
}
One of the big problems is that string refs are now deprecated, and most of the old answers rely on this. It's also not clear to me how to use the new useMap hook in concert with the createRef method in a function component. (The third approach above seems to get the closest, and is from just a year ago, but it still relies on componentDidMount.) Finally, I am unable to get my FeatureGroup to call either the onadd or onAdd method (it's unclear which is correct? see comment below the answer…) when I pass it to the component. The 2018 answer relies on this callback.
The combination of these three issues mean I can't get any of the old solutions working. So I thought I'd ask anew: what's the right way to do this these days?
For the question described in the title, as you have another one later regarding onAdd and it is better to ask it separately in my opinion, you can still use a ref to get a reference to the FeatureGroup
All you need is to call map.fitBounds. To get the map reference you need to use whenCreated prop inside the component that includes MapContainer. If you were inside a child you would have to use useMap hook to get it. Once you get this you need to use the featureGroup ref which is a react ref, an object actually, and can be accessed via current. Inside there you have some leaflet methods. The one you need is getBounds method to retrieve the bounds of your feature group.
const [map, setMap] = useState(null);
const featureGroupRef = useRef();
useEffect(() => {
if (!map) return;
map.fitBounds(featureGroupRef.current.getBounds());
}, [map]);
<MapContainer
center={coordinates}
zoom={6}
style={{
height: "400px",
width: "400px"
}}
whenCreated={setMap}
>
...
<FeatureGroup ref={featureGroupRef}>{circle}</FeatureGroup>
</MapContainer>
Demo

InfoWindow is showing two infowindows using react-google-maps

I'm using the react-google-maps library and I've been doing pretty well so far. My app receives a json with locations from my backend and I'm mapping them to add Markers on the map, they are displaying just fine. These markers have an OnClick function which sets information on a ReactHook.
When my hook state changes, my InfoWindow triggers and displays itself and this is fine. The problem is a little InfoWindow with no information which triggers at the same time than the other, i have tried to find the error for a few hours now and I can't find it, I've checked the code and also I'be inspect the components from the Browser but that little InfoWindow is not being recognized by the react developer tools.
Here is my code:
function Map(props){
const [selectedTienda, setSelectedTienda] = useState(null)
const options ={
styles: MapStyles,
disableDefaultUI: true,
}
return(
<GoogleMap
defaultZoom={17}
defaultCenter={{lat: 29.0824139, lng: -110.966389}}
options={options}
>
{props.items.map(tienda =>(
<Marker
key={tienda['tienda.id']}
position={{
lat: parseFloat(tienda['tienda.ubicacion.lat']),
lng: parseFloat(tienda['tienda.ubicacion.lng'])
}}
onClick={()=>{
setSelectedTienda(tienda);
}}
/>
))}
{selectedTienda ? (
<InfoWindow position={{
lat: parseFloat(selectedTienda['tienda.ubicacion.lat']),
lng: parseFloat(selectedTienda['tienda.ubicacion.lng'])
}}
onCloseClick={()=>{setSelectedTienda(null)}}
>
<div><h4>This is the <br/>INFOWINDOW I WANT</h4></div>
</InfoWindow>
): null}
</GoogleMap>
)
}
const MyMapComponent = withScriptjs(withGoogleMap(Map))
Here is also an image of my nightmare:
Here is what my react dev tools is showing, which is only the main InfoWindow
So the thing that was giving me a headache was the React Strict Mode wrapper on the index.js, which renders my component twice. According to the React documentation,
StrictMode is a tool that helps highlight potential problems in application.
Like Fragment, StrictMode does not render any visible UI. It activates additional checks and warnings for its descendants.
StrictMode currently helps with:
Identifying components with unsafe lifecycles
Warning about legacy string ref API usage
Warning about deprecated findDOMNode usage
Detecting unexpected side effects
Detecting legacy context API
So in my case, the problem was that StrictMode was detecting unexpected side effects on my map component, which was intentionally double-invoking my useState function, making two InfoWindows appear, instead of just one.
My solution was to remove the React.StrictMode wrapper from my index.js file, and that was it, but I think there might be another solution to avoid those unexpected side effects.
More information on StrictMode and how it works:
https://reactjs.org/docs/strict-mode.html
I found a solution that works without removing the React.StricMode wrapper:
Just use InfoWindowF instead of InfoWindow (that applies to the html tags as well).
AirLancer's solution to use InfoWindowF worked for me too. Using MarkerF instead of Marker also helped solve some other bugs (not rendering immediately). According to another forum these are issues in React 18+
How to use InfoWindowF?
is this the way?
import {
InfoWindowF,
} from "#react-google-maps/api";
{selectedMarker ? (
<InfoWindowF
position={{ lat: selectedMarker.lat, lng: selectedMarker.lng }}
onCloseClick={(event) => handleInfoWindow(event)}
onOpen={() => {
console.log("InfoWindow opened!");
}}
onClose={() => {
console.log("InfoWindow closed!");
}}
children={<p>{infoData.Name}</p>}
></InfoWindowF>
) : null}

Using Stateful React classes in typescipt

I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.

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.

Resources