import React from 'react';
import {GoogleMap, withScriptjs, withGoogleMap, Marker} from 'react-google-maps';
import {db} from './Firebase';
import {useState, useEffect} from 'react';
import InfoWindow from 'react-google-maps/lib/components/InfoWindow';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import { CssBaseline } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import IconButton from '#material-ui/core/IconButton'
import KeyboardArrowLeftRounded from '#material-ui/icons/KeyboardArrowLeftOutlined';
const useStyles = makeStyles( theme =>({
appBar: {
backgroundColor: '#1976d2'
},
card :{
marginTop: 60
}
}));
const Maps = (props) =>
{
const classes = useStyles();
const [positions, setPosition] = useState([])
useEffect(() => {
const unsub = db.collection('Location').onSnapshot (snapshot => {
const allPositions = snapshot.docs.map(doc => ({
id: doc.id,
... doc.data()
}));
setPosition(allPositions);
})
return () => {
unsub();
};
}, [])
const WrappedMap = withScriptjs(withGoogleMap(props => (
<GoogleMap defaultZoom = {13}
defaultCenter = {{ lat: -1.292066 , lng : 36.821945}}>
{
positions.map(positioning => (
props.isMarkerShown &&
<Marker key = {positioning.id}
position = {{lat: positioning.Latitude , lng: positioning.Longitude}}
></Marker>
)
)
}
{
positions.map(positioning => (
<InfoWindow key = {positioning.id} defaultPosition = {{lat: positioning.Latitude, lng: positioning.Longitude}}>
<div>
{positioning.Team} <br/>
Message
</div>
</InfoWindow>
))
}
</GoogleMap>)));
return (
<div>
<div>
<CssBaseline/>
<AppBar position = "fixed" color = "primary" className = {classes.appBar}>
<Toolbar>
<IconButton color = "inherit" edge = "start" onClick={() => props.history.goBack()}>
<KeyboardArrowLeftRounded/>
</IconButton>
</Toolbar>
</AppBar>
</div>
<div>
<Card className = {classes.card}>
<CardContent>
<div style = {{width: "97vw", height: "90vh"}}>
<WrappedMap
isMarkerShown
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</CardContent>
</Card>
</div>
</div>
)
};
export default Maps;
I am using the code above to render markers and infowindows for locations that I'm fetching from Firebase. Currently when an update is made in Firebase, the whole mapview is rendered again in order to update the position of the markers and infowindows. How do I go about re-rendering just the marker when an update is made in Firebase.
In your example WrappedMap gets re-created every time. One solution (to prevent Google Maps API reload and maps re-render) would be to place the instantiation of WrappedMap component outside of Maps:
const WrappedMap = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultZoom={5}
defaultCenter={{ lat: -24.9929159, lng: 115.2297986 }}
>
{props.places.map(
position =>
props.isMarkerShown && (
<Marker
key={position.id}
position={{
lat: position.lat,
lng: position.lng
}}
></Marker>
)
)}
</GoogleMap>
))
);
and then just pass positions prop to reflect updated positions on map:
function Map() {
return (
<div>
<WrappedMap
isMarkerShown
positions={positions}
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
You can prevent re-rendering by making use of PureComponent.
In your case positioning is re-rendering the Marker and InfoWindow.
The Updated Code below will only re-render the updated/newly-added Markers.
import React from 'react';
import {GoogleMap, withScriptjs, withGoogleMap, Marker} from 'react-google-maps';
import {db} from './Firebase';
import {useState, useEffect} from 'react';
import InfoWindow from 'react-google-maps/lib/components/InfoWindow';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import { CssBaseline } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import IconButton from '#material-ui/core/IconButton'
import KeyboardArrowLeftRounded from '#material-ui/icons/KeyboardArrowLeftOutlined';
const useStyles = makeStyles( theme =>({
appBar: {
backgroundColor: '#1976d2'
},
card :{
marginTop: 60
}
}));
class MarkerPureComponent extends React.PureComponent {
render () {
return (
<Marker
position = {this.props.position}
>
</Marker>
)
}
}
class InfoWindowPureComponent extends React.PureComponent {
render () {
return (
<InfoWindow defaultPosition = {this.props.defaultPosition}>
{this.props.children}
</InfoWindow>
)
}
}
const Maps = (props) =>
{
const classes = useStyles();
const [positions, setPosition] = useState([])
useEffect(() => {
const unsub = db.collection('Location').onSnapshot (snapshot => {
const allPositions = snapshot.docs.map(doc => ({
id: doc.id,
... doc.data()
}));
setPosition(allPositions);
})
return () => {
unsub();
};
}, [])
const WrappedMap = withScriptjs(withGoogleMap(props => (
<GoogleMap defaultZoom = {13}
defaultCenter = {{ lat: -1.292066 , lng : 36.821945}}>
{
positions.map(positioning => (
props.isMarkerShown &&
<MarkerPureComponent key = {positioning.id}
position = {{lat: positioning.Latitude , lng: positioning.Longitude}}
></MarkerPureComponent>
)
)
}
{
positions.map(positioning => (
<InfoWindowPureComponent key = {positioning.id} defaultPosition = {{lat: positioning.Latitude, lng: positioning.Longitude}}>
<div>
{positioning.Team} <br/>
Message
</div>
</InfoWindowPureComponent>
))
}
</GoogleMap>)));
return (
<div>
<div>
<CssBaseline/>
<AppBar position = "fixed" color = "primary" className = {classes.appBar}>
<Toolbar>
<IconButton color = "inherit" edge = "start" onClick={() => props.history.goBack()}>
<KeyboardArrowLeftRounded/>
</IconButton>
</Toolbar>
</AppBar>
</div>
<div>
<Card className = {classes.card}>
<CardContent>
<div style = {{width: "97vw", height: "90vh"}}>
<WrappedMap
isMarkerShown
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</CardContent>
</Card>
</div>
</div>
)
};
export default Maps;
Related
I want to pass the selected date as a parameter when the showtime button is clicked. I couldn't find a way and I'm new to React. looking for suggestions.
Following is my code
import React, { useEffect, useState }from "react";
import PropTypes from "prop-types";
import { makeStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import PhoneIcon from "#material-ui/icons/Phone";
import FavoriteIcon from "#material-ui/icons/Favorite";
import PersonPinIcon from "#material-ui/icons/PersonPin";
import HelpIcon from "#material-ui/icons/Help";
import ShoppingBasket from "#material-ui/icons/ShoppingBasket";
import ThumbDown from "#material-ui/icons/ThumbDown";
import ThumbUp from "#material-ui/icons/ThumbUp";
import Typography from "#material-ui/core/Typography";
import Box from "#material-ui/core/Box";
import { DateRangeSharp } from "#material-ui/icons";
import { List, ListItem, ListItemText } from "#material-ui/core";
import Button from '#material-ui/core/Button';
import queryString from 'query-string';
import { Link, useLocation } from 'react-router-dom';
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`scrollable-force-tabpanel-${index}`}
aria-labelledby={`scrollable-force-tab-${index}`}
{...other}
>
{value === index && (
<Box p={3}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired
};
function a11yProps(index) {
return {
id: `scrollable-force-tab-${index}`,
"aria-controls": `scrollable-force-tabpanel-${index}`,
};
}
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
width: "100%",
backgroundColor: theme.palette.background.paper
}
}));
export default function ScrollableTabsButtonForce() {
const classes = useStyles();
const [value, setValue ] = React.useState(0);
const [venuesDates, setvenuesDates] = React.useState([]);
const [showtimesData] = React.useState([]);
useEffect(() => {
const url = "http://sandbox-api.tickets.lk/v1/movie/3232/showtime";
const requestOptions = (token) => {
return ({
method: 'GET',
headers: { 'Content-Type': 'application/json', 'client_token': 'ebd86470-7e90-4ece-9e89-1b6d4d2cbb61' }
})
};
const fetchData = async () => {
try {
const response = await fetch(url, requestOptions());
const json = await response.json();
// console.log(json);
// console.log(json.data.venueDateShowtime)
setvenuesDates(json.data.venueDateShowtime);
}
catch (error) {
console.log("error",error);
}
};
fetchData();
})
const search = useLocation().search;
const Mid = new URLSearchParams(search).get('movieid');
const Mname = new URLSearchParams(search).get('name');
// console.log(Mname)
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div className={classes.root}>
<AppBar
position="static"
style={{ background: "#333545", minHeight: 60 }}
>
<Tabs
value={value}
onChange={handleChange}
variant="scrollable"
scrollButtons="on"
indicatorColor="secondary"
textColor="primary"
aria-label="scrollable force tabs example"
style={{ minHeight: 60 }}
wrapped
>
{venuesDates.map((showtdates) => {
return (
<Tab
label={showtdates.date}
{...a11yProps(0)}
style={{ color: "#fff", fontSize: 20, minHeight: 60 }}
/>
);
})}
</Tabs>
</AppBar>
{/* map over dates and create TabPanel */}
{/* // check if theater property exists and create a list of theaters as an example */}
{venuesDates.map((date, idx) => {
const venues = date.hasOwnProperty("venues")
? date.venues.map((venues) => (
<ListItem>
<ListItemText
primary={venues.venue}
secondary={venues.venueId}
/>
{date.venues.map((date)=> {
const showtimes = venues.hasOwnProperty("showtimes")
? venues.showtimes.map((showtimes) => (
// <ListItemText primary={showtimes.showtime} />
<Link to={'/seat-booking?movieid=' + Mid +"&name=" + Mname + "&theater=" + venues.venueId +
"&movieDate=" + "showtdates.date" + "&showtimes=" + showtimes.showtimeId}
style={{ textDecoration: 'none', color: 'white' }}>
<Button variant="contained" color="primary" style={{margin:5}}>
{showtimes.showtime}
</Button>
</Link>
))
:null;
return(
<List>{showtimes}</List>
)
})}
</ListItem>
))
: null;
return (
<TabPanel value={value} index={idx}>
<List>{venues}</List>
</TabPanel>
);
})}
</div>
);
}
I really want to get the selected date from the Tab heading and pass as a parameter as mentioned below.
I need to change the "&movieDate=" + "showtdates.date" ===> as "&movieDate=" + (selected tab heading name)
{date.venues.map((date)=> {
const showtimes = venues.hasOwnProperty("showtimes")
? venues.showtimes.map((showtimes) => (
// <ListItemText primary={showtimes.showtime} />
<Link to={'/seat-booking?movieid=' + Mid +"&name=" + Mname + "&theater=" + venues.venueId +
"&movieDate=" + "showtdates.date" + "&showtimes=" + showtimes.showtimeId}
style={{ textDecoration: 'none', color: 'white' }}>
<Button variant="contained" color="primary" style={{margin:5}}>
{showtimes.showtime}
</Button>
</Link>
))
:null;
return(
<List>{showtimes}</List>
)
})}
This app shows Github issues with graphql API.
I didn't change anything after finishing the app but I got this error.
I used Next js, Typescript, Material UI, Tailwind css and GraphQL for this project.
Index Component
import React, { useState } from "react"
import { Typography, Container, makeStyles } from "#material-ui/core"
import SearchBar from "../components/SearchBar/SearchBar"
import RepositoryList from "../components/RepositoryList/RepositoryList"
import Head from "next/head"
const useStyles = makeStyles({
title: {
marginTop: "1rem",
marginBottom: "1rem",
textAlign: "center",
},
})
const App = () => {
const classes = useStyles()
const [searchTerm, setSearchTerm] = useState<string>("")
return (
<>
<Head>
<title>GraphQL Github Client</title>
</Head>
<Container maxWidth={"sm"}>
<div className="mt-10 mb-5">
<Typography variant={"h3"} className={classes.title}>
GraphQL Github Client
</Typography>
</div>
<SearchBar
className="mb-10"
value={searchTerm}
onChange={setSearchTerm}
/>
<RepositoryList searchTerm={searchTerm} />
</Container>
</>
)
}
export default App
RepositoryList Component
import React, { useEffect, useState } from "react"
import { Typography, CircularProgress, makeStyles } from "#material-ui/core"
import { useQuery } from "#apollo/react-hooks"
import { SEARCH_FOR_REPOS } from "../../Queries/queries"
import Repository from "../Repository/Repository"
interface RepositoryListProps {
searchTerm?: string
}
const useStyles = makeStyles({
note: {
marginTop: "1rem",
textAlign: "center",
},
spinnerContainer: {
display: "flex",
justifyContent: "space-around",
marginTop: "1rem",
},
})
const RepositoryList: React.FC<RepositoryListProps> = ({ searchTerm }) => {
const classes = useStyles()
const [expandedRepo, setExpandedRepo] = useState(null)
const { data, loading, error } = useQuery(SEARCH_FOR_REPOS, {
variables: { search_term: searchTerm },
})
useEffect(() => {
setExpandedRepo(null)
}, [data])
if (loading) {
return (
<div className={classes.spinnerContainer}>
<CircularProgress />
</div>
)
}
if (error) {
return (
<Typography
variant={"overline"}
className={classes.note}
component={"div"}
color={"error"}
>
{error}
</Typography>
)
}
if (!data.search.repositoryCount) {
return (
<Typography
variant={"overline"}
className={classes.note}
component={"div"}
>
There are no such repositories!
</Typography>
)
}
return (
<div>
{data.search.edges.map(
(
repo: { edges: { id: number } },
i: string | number | ((prevState: null) => null) | null | any
) => (
<>
<Repository
repo={repo}
expanded={expandedRepo === i}
onToggled={() => setExpandedRepo(i)}
key={repo.edges.id}
/>
</>
)
)}
</div>
)
}
export default RepositoryList
Repository Component
import React from "react"
import {
ExpansionPanel,
ExpansionPanelSummary,
ExpansionPanelDetails,
Typography,
Chip,
makeStyles,
} from "#material-ui/core"
import StarIcon from "#material-ui/icons/Star"
import PeopleIcon from "#material-ui/icons/People"
import IssueList from "../IssueList/IssueList"
const useStyles = makeStyles({
root: {
marginTop: "1rem",
},
summaryContainer: {
flexDirection: "column",
},
summaryHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginBottom: "1rem",
},
chip: {
marginLeft: "0.5rem",
},
})
interface RepositoryProps {
repo: any
expanded: boolean
onToggled: any
}
const Repository: React.FC<RepositoryProps> = ({
repo,
expanded,
onToggled,
}) => {
const {
node: {
name,
descriptionHTML,
owner: { login },
stargazers: { totalCount: totalStarCount },
},
} = repo
const classes = useStyles()
return (
<ExpansionPanel
expanded={expanded}
onChange={onToggled}
className={classes.root}
>
<ExpansionPanelSummary classes={{ content: classes.summaryContainer }}>
<div className={classes.summaryHeader}>
<Typography variant={"h6"}>{name}</Typography>
<div>
<Chip
label={`by ${login}`}
avatar={<PeopleIcon />}
className={classes.chip}
/>
<Chip
label={totalStarCount}
avatar={<StarIcon />}
className={classes.chip}
/>
</div>
</div>
<Typography
variant={"caption"}
dangerouslySetInnerHTML={{ __html: descriptionHTML }}
component={"div"}
/>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
{expanded && <IssueList repoName={name} repoOwner={login} />}
</ExpansionPanelDetails>
</ExpansionPanel>
)
}
export default Repository
These are my components.
What should I do for fixing this problem?
It looks like the issue is in this spot where you do {error}. I would double check what error actually is but it looks like its an object and not a string like you are using it
<Typography
variant={"overline"}
className={classes.note}
component={"div"}
color={"error"}
>
{error}
</Typography>
I'm doing a little app using React Leaflet, and I want to add a marker on the map every time I click somewhere,
I don't know how to do it, I tried something below but the console log doesn't return something,
function App() {
const [position, setPosition] = useState([48.8534, 2.3488]);
const [markers, setMarkers] = useState([]);
function addMarker(e) {
console.log("e", e);
const newMarker = e;
setMarkers([...markers, newMarker]);
}
return (
<div className="App" style={{ width: "100%", height: "100vh" }}>
<MapContainer
center={position}
zoom={6}
scrollWheelZoom={true}
style={{ width: "100%", height: "100vh" }}
onClick={addMarker}
>
<MyComponent />
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
{markers &&
markers.map((marker, index) => {
<Marker key={`marker-${index}`} position={marker}>
<Popup>
<span>Popup</span>
</Popup>
</Marker>;
})}
</MapContainer>
</div>
);
}
export default App;
react-leaflet just updated to version 3, which no longer support inline events like onClick. You need to use the useMapEvents hook. Example here.
import { MapContainer, useMapEvents } from 'react-leaflet'
function AddMarkerToClick() {
const [markers, setMarkers] = useState([]);
const map = useMapEvents({
click(e) {
const newMarker = e.latlng
setMarkers([...markers, newMarker]);
},
})
return (
<>
{markers.map(marker =>
<Marker position={marker}>
<Popup>Marker is at {marker}</Popup>
</Marker>
)}
</>
)
}
function App() {
return (
<div className="App" style={{ width: "100%", height: "100vh" }}>
<MapContainer {...} > {/* omit onClick */}
<AddMarkerToClick />
</MapContainer>
</div>
);
}
export default App;
I didn't get a chance to test this yet, but this should give you an idea.
import React, { memo, useEffect, useState } from 'react'
import { Map, TileLayer } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import Markers from './Markers'
const initialState = ({
lat: '18.4942031',
lng: '-69.8919176',
zoom: '13'
})
export const MapView = memo(({dataMap, searchHistory}) => {
const [ properties, setProperties ] = useState(initialState)
const setPropertiesOnMap = () =>{
setTimeout(function(){
setProperties({
lat: dataMap[0]?.latitude || '18.4942031',
lng: dataMap[0]?.longitude || '-69.8919176',
zoom: '18',
})
},500)
}
useEffect(() =>{
if(dataMap.length === 1){
setPropertiesOnMap()
}else{
setProperties(initialState)
}
//eslint-disable-next-line
},[dataMap])
return (
<Map center={{lat: properties.lat, lng: properties.lng}} zoom={properties.zoom} minZoom={8}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
{dataMap[0] && <Markers dataMap={dataMap} searchHistory={searchHistory} />}
</Map>
)
})
I did something similar, I hope it can help you
My objective is to pan google-maps-react map to a latlng position, after getting a latlong from react-places-autocomplete when a user selects an address suggestion.
I am facing difficulty in setting ref of map from a child functional component, so that I can call map.panTo(location) in the parent functional component.
Following is my Google-Maps and PlaceAutoComplete child Component:
import React, { useEffect } from 'react';
import { Map, GoogleApiWrapper, Marker } from 'google-maps-react';
import { FormGroup, Label, Input, Spinner, Container, Row, Col } from 'reactstrap';
import PlacesAutocomplete from 'react-places-autocomplete';
const InputAndMap = React.forwardRef((props, ref) => {
return (
<div>
<PlacesAutocomplete
value={props.address}
onChange={props.handleInputChange}
onSelect={props.handleInputSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<FormGroup>
<Label for="exampleSearch">Search Address</Label>
<Input
{...getInputProps({
className: 'location-search-input',
})}
type="search"
name="search"
id="exampleSearch"
placeholder="Enter Store Location"
/>
</FormGroup>
<div className="autocomplete-dropdown-container">
{loading && (
<div>
<Spinner size="sm" color="primary" />
Loading...
</div>
)}
{suggestions.map(suggestion => {
const className = suggestion.active ? 'suggestion-item--active' : 'suggestion-item';
const style = suggestion.active
? { backgroundColor: '#007bff', cursor: 'pointer', color: 'white' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<Row className="mb-3" style={{ width: '100%', height: '200px' }}>
<Col>
<Map
id="google-map"
ref={ref} // <<=== setting ref here
style={{ width: '100%', height: '200px' }}
google={props.google}
zoom={8}
initialCenter={{ lat: 47.444, lng: -122.176 }}
onClick={(t, map, e) => props.updateMarker(e.latLng, map)}
>
{props.markerLatLong && <Marker position={props.markerLatLong} />}
</Map>
</Col>
</Row>
</div>
);
});
export default GoogleApiWrapper({
apiKey: process.env.REACT_APP_GOOGLE_API_KEY,
libraries: ['places'],
})(InputAndMap);
This is my parent component, where I want to call the map panto function.
import React, { useState, useEffect } from 'react';
import { Button, Form, Spinner, Container } from 'reactstrap';
import { Redirect } from 'react-router-dom';
import { geocodeByAddress, getLatLng } from 'react-places-autocomplete';
import firebase from 'firebase/app';
import NavBarMenu from '../components/NavBarMenu';
import InputAndMap from '../components/InputAndMap';
import fire from '../config/fire';
function StoreScreen(props) {
const [isLoading, setIsLoading] = useState(false);
const [markerLatLong, setMarkerLatLong] = useState(null);
const [city, setCity] = useState('');
const [address, setAddress] = useState('');
const [redirect, setRedirect] = useState(false);
const ref = React.createRef();
const handleInputChange = address => {
setAddress(address);
};
const handleInputSelect = address => {
setAddress(address);
geocodeByAddress(address)
.then(results => {
processCity(results);
getLatLng(results[0])
.then(latLng => {
console.log('Success', latLng);
console.log(ref);// ==============> this return {current: null}
// ref.current.panTo(latLng);// ==> So I am unable to call this
})
.catch(error => console.error('Error', error));
})
.catch(error => console.error('Error', error));
};
return (
<div>
<NavBarMenu isShopKeeper />
<Container className="h-100">
<Form onSubmit={handleSubmit}>
<h5 className="text-center">Add Store</h5>
<InputAndMap
ref={ref}
markerLatLong={markerLatLong}
updateMarker={updateMarker}
handleInputChange={handleInputChange}
handleInputSelect={handleInputSelect}
address={address}
/>
{isLoading ? (
<div className="row mx-auto justify-content-center align-items-center flex-column">
<Spinner color="secondary" />
</div>
) : (
<Button
disabled={!markerLatLong || !city || !address}
className="mb-4"
color="primary"
size="lg"
block
>
Add Store
</Button>
)}
</Form>
</Container>
</div>
);
}
export default StoreScreen;
I am also attaching the image for better visualizing my problem.
Map.panTo changes the center of the map to the given LatLng in Maps JavaScript API. Since you are using google-maps-react library, you can use react states as value of the center parameter of this library to change the value of the Map's center everytime the state changes. In my example code below, I use the code from the getting started docs of react-places-autocomplete and incorporated it with a simple google-maps-react code.
Here's how I declare the state of the center which currently have a value:
state = {
center: {
lat: 40.854885,
lng: -88.081807
},
address: ""
};
Here's the handleSelect event from the react-places-autocomplete library where it geocodes the selected place from the autocomplete. Then you can see that I set the state of the center to the latLng of the geocoded address.
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.setState({ center: latLng }))
.catch(error => console.error("Error", error));
};
Here's how I call the Map component of the google-maps-react library where the value of center parameter is the value of the state named center.
<Map
className="map"
google={this.props.google}
onClick={this.onMapClicked}
center={this.state.center}
style={{ height: "100%", position: "relative", width: "100%" }}
zoom={13}
/>
Here's a complete code snippet and the working code on how I incorporated the 2 libraries you are using to change the center of the map everytime you choose an address from autocomplete:
import React, { Component } from "react";
import { Map, GoogleApiWrapper } from "google-maps-react";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
export class MapContainer extends Component {
state = {
center: {
lat: 40.854885,
lng: -88.081807
},
address: ""
};
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.setState({ center: latLng }))
.catch(error => console.error("Error", error));
};
render() {
if (!this.props.loaded) return <div>Loading...</div>;
return (
<div>
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({
getInputProps,
suggestions,
getSuggestionItemProps,
loading
}) => (
<div>
<input
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input"
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<Map
className="map"
google={this.props.google}
center={this.state.center}
style={{ height: "100%", position: "relative", width: "100%" }}
zoom={13}
/>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey: "YOUR_API_KEY"
})(MapContainer);
I am trying to re-render the google map component when the state changes. I am using react-redux to change the state. The state changes successfully, but the map doesn't re-render. When I manually refresh the browser then only it takes new state value.
map.js
import React from 'react';
import { compose, withProps,withHandlers } from "recompose"
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from "react-google-maps"
import { MarkerClusterer } from 'react-google-maps/lib/components/addons/MarkerClusterer';
import { connect } from 'react-redux';
const MyMapComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=AIzaSyAoaDS6fIKlYvEHeTaakCxXqp-UwnggoEg",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withHandlers({
onMarkerClustererClick: () => (markerClusterer) => {
const clickedMarkers = markerClusterer.getMarkers()
console.log(`Current clicked markers length: ${clickedMarkers.length}`)
console.log(clickedMarkers)
},
}),
withScriptjs,
withGoogleMap
)((props) =>
<GoogleMap
defaultZoom={props.maps.zoom}
defaultCenter={{ lat:props.maps.lat, lng:props.maps.lng }}
>
<MarkerClusterer
onClick={props.onMarkerClustererClick}
averageCenter
enableRetinaIcons
gridSize={60}
>
{props.markers.map(marker => (
<Marker
key={marker.photo_id}
position={{ lat: marker.latitude, lng: marker.longitude }}
/>
))}
</MarkerClusterer>
</GoogleMap>
);
export default class DemoApp extends React.Component {
componentWillMount() {
this.setState({ markers: [] })
}
componentDidMount() {
console.log("+mymap++++++++");
console.log(this.props.myMap);
this.setState({markers:[{photo_id:1,longitude:76.911270,latitude:11.032595},
{photo_id:2,longitude:75.806682,latitude:11.259169},
{photo_id:3,longitude:77.213780,latitude:28.617671},
{photo_id:4,longitude:78.138991,latitude:9.903245}]})
}
render() {
return (
<MyMapComponent markers={this.state.markers} maps={this.props.myMap} />
)
}
}
In the above code I am giving dynamic values to the defaultcenter property of google map.
home.js
import React from 'react';
import ReactDom from 'react-dom';
import User from './users';
import * as ReactBootstrap from 'react-bootstrap';
import { connect } from 'react-redux';
import Card from './userdetails';
import Paper from 'material-ui/Paper';
import Menu from 'material-ui/Menu';
import MenuItem from 'material-ui/MenuItem';
import MapIcon from 'material-ui/svg-icons/communication/location-on';
import './homeStyle.css';
import DemoApp from './maps';
class Home extends React.Component {
viewProfile(e) {
console.log(e);
console.log("component in home");
this.props.userSelected(e);
}
render() {
const style = {
paper: {
display: 'inline-block',
float: 'left',
margin: '16px 32px 16px 0',
}
}
return (
<div style={{marginTop:68}}>
<h1>welcome </h1>
<ReactBootstrap.Col sm={12} >
<ReactBootstrap.Row>
<ReactBootstrap.Col sm={4}>
<ReactBootstrap.Col sm={12}>
<Paper style={style.paper}>
<Menu onItemClick={(event,menuitem,index)=>{console.log(menuitem.props.value);this.props.myMap();}} value="map menu">
<MenuItem value="maps" primaryText="My Location" leftIcon={<MapIcon />} />
</Menu>
</Paper>
</ReactBootstrap.Col>
<ReactBootstrap.Col sm={12}>
<DemoApp myMap={this.props.selectedMap} />
</ReactBootstrap.Col>
</ReactBootstrap.Col>
<ReactBootstrap.Col sm={4} style={{border:'1px solid black',paddingBottom:20,marginBottom:78,marginTop:10}}>
<User viewProfile={(e) => this.viewProfile(e)} />
</ReactBootstrap.Col>
{ this.props.selectedUser.id ?
<ReactBootstrap.Col sm={4} style={{border:'1px solid black',marginTop:10}}>
<Card details={this.props.selectedUser} />
</ReactBootstrap.Col>
: null
}
</ReactBootstrap.Row>
</ReactBootstrap.Col>
</div>
)
}
}
const mapStateProps=(state)=> {
console.log(state);
return {
selectedUser:state.userdetails,
selectedMap:state.mapReducer
}
}
const mapDispatchProps=(dispatch)=> {
return {
userSelected:(data)=> {
dispatch({type:"USER_SELECTED",data});
},
myMap:()=>{
dispatch({type:"MAPS"});
}
}
}
export default connect(mapStateProps,mapDispatchProps)(Home);
In the above code (home.js) <DemoApp /> is the map component and the state properties are passed through the props to it. When the user clicks on the menu item it dispatches an action called 'MAPS' and the state changes successfully, but the <DemoApp /> component doesn't re-render with its new values. What is the issue here?