Google Maps React Markers and List - reactjs

I was wondering if anyone has used google-maps-react library to create Markers and List Items. I have a map component that is rendering the Map, Markers and Info Window. I also have another component that has all List (li) of places that represent each Marker's value. Is there someway that I can click on the list item, and trigger the appropriate Marker's click event.

If List is holding the information of your Markers, you need to uplift state as you are trying to communicate from Child to Parent. So ideally, Markers positions live in the parent, as well as the function to call (Map in your case). And you call it from your list item passing the function via props to the List component like so:
<List handleMarkers={this.function}/>
Access the function in the child via this.props.handleMarkers.

I guess you mean google-maps-react library, right?
Lets say you have a component to select an address:
const CityList = props => {
return (
<div>
<ul>
{props.items.map((item, index) => {
return (
<li key={index} onClick={e => props.onClick(e, item)}>
{item.title}
</li>
);
})}
</ul>
</div>
);
};
Then you could introduce a selected value as state and update the state once the address list item is clicked. Updating the state will trigger a rerender of the Map component:
class App extends Component {
state = {
selectedItem: { lat: 0, lng: 0 }
};
showInfo(e, selectedItem) {
this.setState({ selectedItem: selectedItem });
}
render() {
return (
<div>
<CityList items={data} onClick={this.showInfo.bind(this)} />
<MapContainer
center={{ lat: -24.9923319, lng: 135.2252427 }}
zoom={4}
data={data}
selectedItem={this.state.selectedItem}
/>
</div>
);
}
}
Here is a demo for your reference

Related

How to check if all instances of a React Component have the same value for their state?

So right now, in my App file I have this:
{items.map(el => (
<Item
prop1={foo}
prop2={bar}
el={baz}
/>
))}
And in the <Item> component, I have this:
<span className={finishedClass ? "finishedItem" : ""}>
{props.el}
</span>
where finishedClass is a state variable.
So my question is, how can I check if finishedClass is true for every single <Item> component that gets generated as a result of the items.map?
So, you basically want to know if all the finishedClass state values in all the Item components are true. This can be simplified in the sense that if any of the Item components have finishedClass as false, you will perform a certain action.
So, what you can do is, pass a function as a prop to the Item component as follows:
{items.map(el => (
<Item
prop1={foo}
prop2={bar}
el={baz}
setFinishedClassToFalse={()=>{/*your statements*/}}
/>
))}
This function setFinishedClassToFalse will be called by the Item component if and only if its state value finishedClass is false. Obviously, there's a little more implementation to this than what I have described. But this should give you a start.
Parent component can communication with child component with parent props that have function in it. You can see code below onFinished property in Parent component has handleFinished() function value in it. That fucntion will be a bridge to help Child component to communicate with Parent component. On the Child component you must run onFinished props to triggering handleFinished function on Parent comoponent. In code below the trigger for props.onFinished is when <span> clicked by user.
import React from "react";
import ReactDOM from "react-dom";
import { Grid } from "react-flexbox-grid";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { data: "test" };
}
render() {
const items = ["red", "green", "blue"];
const handleFinished = (e, index) => {
this.setState({ data: e });
console.log(this.state);
};
return (
<Grid>
{items.map((el, index) => (
<Child
prop1={"test"}
prop2={"test1"}
el={el}
onFinished={(e) => handleFinished(e, index)}
/>
))}
<div>{this.state.data.toString()}</div>
</Grid>
);
}
}
const Child = (props) => {
// example for your finishedClass value state
const finishedClass = true;
return (
<span
onClick={() => props.onFinished(finishedClass)}
className={finishedClass ? "finishedItem" : ""}
>
{props.el}{" "}
</span>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
As finishedClass is a state variable, if you just console.log(finishedClass) inside component it will just do the work

Jest: functional component array list not mapping after adding element

I am trying to list person by clicking onClick function to add empty object so that map can loop
over and show div. I see prop onclick function calling works but i map is not looping over it.
a little help would be appreciated.
// functional component
const listing = ({list=[]}) => (
<>
{
<div id='addPerson' onClick={() => list.push({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
);
export default listing;
// test case
it('renders list-items', () => {
const wrapper = shallow(<listing />);
wrapper.find('#addPerson').prop('onClick')();
console.log(wrapper.find('.listItems').length); // showing zero, expected 1
});
Updating List is not going to cause a re-render in your component. I am not sure where List is coming from, but I am assuming it is coming from a local state in the parent, or probably redux store. From inside your component, you will have to call a function in your parent to update the list array there.
Below is a version of your component, assuming the list in your local state. You will have to adjust the code to update the state in the parent or redux store, but the below should be enough to give you an idea of what to do. Also, don't push the element to array, as it mutates the object and will not contact re-render.
const Listing = ({list = [], addToList}) => {
return <>
{
<div id='addPerson' onClick={() => addToList({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
};

Get child component state in parent component

I'm making a contact list where you can add contacts to your favorites. Then filter my favorite contacts.
First all contacts have the state isFavorite: false, then I click on one contact, click on the star that sets isFavorite: true. I close that contact and click on the filter button, to see all my favorite contacts
so in here I add a contact to my favorites:
ContactName.js
state = {
isFavorite: false
}
handleFavorite = () => {
this.setState({
isFavorite: !this.state.isFavorite
})
}
render() {
return (
<React.Fragment>
<li onClick={this.handleClick}>
{this.props.contact.name}
</li>
{
this.state.isOpen ?
<Contact
contact={this.props.contact}
close={this.handleClick}
favorite={this.handleFavorite}
isFavorite={this.state.isFavorite}
/>
: null
}
</React.Fragment>
)
}
Contact.js
<Favorites
id={contact.id}
name={contact.name}
onClick={this.props.favorite}
state={this.props.isFavorite}
/>
Favorites.js
this is just where the favorite component is
<span onClick={this.props.onClick}>
{
!this.props.state
? <StarBorder className="star"/>
: <Star className="star"/>
}
</span>
and here is where I want to be able to get the isFavorite state. This is the parent component where the button for filtering the contacts is.
ContactList.js
<React.Fragment>
<span
className="filter-button"
>Filtera favoriter</span>
<ul className="contacts">
{
this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map(contact => (
<ContactName
key={contact.id}
contact={contact}
name={contact.name}
/>
))
}
</ul>
</React.Fragment>
You are doing this in the wrong direction.
In the React, you can pass the data down with props (or by using Context which is no the case here). So if you need a data on the ancestor component, the data should be state/props of that ancestor.
In your case, the favorite data should be inside of the contacts (that is defined as props of the ContactName), and you should pass it to the ContactName just like other props.
<React.Fragment>
<span
className="filter-button"
>Filtera favoriter</span>
<ul className="contacts">
{
this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map((contact, index) => (
<ContactName
key={contact.id}
contact={contact}
name={contact.name}
isFavorite={contact.isFavorite}
handleFavorite={() => this.props.handleFavorite(index))}
/>
))
}
</ul>
</React.Fragment>
and inside your ContactName.js
render() {
return (
<React.Fragment>
<li onClick={this.handleClick}>
{this.props.contact.name}
</li>
{
this.state.isOpen ?
<Contact
contact={this.props.contact}
close={this.handleClick}
favorite={this.props.handleFavorite}
isFavorite={this.props.isFavorite}
/>
: null
}
</React.Fragment>
)
}
and toggleFavorite function also should be the same place as the contacts state is.
In React, parent components should not have access to their children's state. Instead, you need to move your isFavorite state up a level to your ContactList component and turn it into a list or map instead of a boolean.
ContactList.js
class ContactList extends React.Component {
state = {
// In this example, `favorites` is a map of contact ids.
// You could also use an array to keep track of the favorites.
favorites: {},
};
render() {
return (
<React.Fragment>
<span className="filter-button">Filter a favorite</span>
<ul className="contacts">
{this.props.contacts
.filter(this.handleSearchFilter(this.props.search))
.map(contact => (
<ContactName
key={contact.id}
contact={contact}
isFavorite={!!this.state.favorites[contact.id]}
name={contact.name}
handleFavorite={() => this.handleFavorite(contact.id)}
/>
))}
</ul>
</React.Fragment>
);
}
handleFavorite = contactId => {
// Use a callback for state here, since you're depending on the previous state.
this.setState(state => {
return {
...state.favorites,
[contactId]: !state.favorites[contactId], // Toggle the value for given contact.
};
});
};
}
Now the handleFavorite and isFavorite props can simply be passed down as needed to your child components.
okay, I've managed to get the childs state in the parent. But now everytime I add a new contact to my favorites, it creates new objects - see codebox https://codesandbox.io/embed/charming-bohr-rwd0r
Is there a way to mash all of those new created objects into one object and set that one big objects equal to a new state called favoriteContacts = []?

Render a dialog window for a particular element inside a map function

I am mapping through an array and on click of an element I would like the details of the element be passed to a dialog window for display. Currently, because of the map function, I always get the last element (the last to be mapped)
I have tried to move the onClick function out of the map, but then I don't get information like waypoint and the index if so.
...
handleUpdateWaypointDialogOpen() {
this.setState({
updateWaypointDialogOpen: true,
})
}
handleUpdateWaypointDialogClose() {
this.setState({
updateWaypointDialogOpen: false,
})
}
render() {
return (
array.map((waypoint, i) => {
return (
<div key={i}>
{
<UpdateWaypointDialog
open={updateWaypointDialogOpen}
onClose={this.handleUpdateWaypointDialogClose}
updateWaypoint={this.updateWaypoint}
index={i}
waypoint={waypoint} />
}
<Card
<CardContent>
<Grid container spacing={16}>
<Grid item xs={12}>
<div onClick={this.handleUpdateWaypointDialogOpen}>
{waypoint.address}
</div>
</Grid>
</Grid>
</CardContent >
</Card >
</div >
)
})
)}
I expect to have only the information of that particular waypoint be passed to the UpdateWaypointDialog and not the full array.
Not sure if I understood you correctly,
But you can always pass additional data from onClose or onClick
handleUpdateWaypointDialogOpen(event, waypoint) {
this.setState({
updateWaypointDialogOpen: true,
})
}
handleUpdateWaypointDialogClose(waypoint) {
this.setState({
updateWaypointDialogOpen: false,
})
}
<UpdateWaypointDialog
open={updateWaypointDialogOpen}
onClose={()=> this.handleUpdateWaypointDialogClose(waypoint)}
updateWaypoint={this.updateWaypoint}
index={i}
waypoint={waypoint} />
<div onClick={(e)=>this.handleUpdateWaypointDialogOpen(e, waypoint)}>
{waypoint.address}
</div>
array.map to another new Component, then move the updateWaypointDialogOpen into that new Component's state, also move <div key={i}><UpdateWaypointDialog ...> into new Component's render method. This way each Component has its own state, isolated from on another.

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

Resources