Can´t re-render a Google Maps with ReactJS - javascript

I´m trying to re-render a google map based on a location(city). I enter the city in the input type, but nothing happens. Can anyone help me with this? I already tried a lot of things, but nothing happens. Regards,
https://pastebin.pl/view/e31aeb6a
[enter link description here][1]

You might be looking for something like this where you will use Geocoding API to Geocode the address from your input then once geocoded, it will get the coordinates and plot it to your map using Maps JavaScript API.
You might also, would like to check Places Autocomplete with Maps JavaScript API where it provides suggestion on the address you are searching for then renders the place you selected on the map.
Please note that if you want to use Google Maps API, you need to use an API key and usage of these APIs are billed accordingly.
Here is how I implemented it on React. Code snippet below:
import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';
var map;
const API_KEY = "PUT_API_KEY_HERE";
class GeocodingApp extends React.Component {
constructor(props) {
super(props);
this.renderMap = this.renderMap.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.state = {
input: ""
};
}
componentDidMount() {
if (!window.google) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `https://maps.googleapis.com/maps/api/js?key=` + API_KEY + `&libraries=geometry,places`;
script.id = 'googleMaps';
script.async = true;
script.defer = true;
document.body.appendChild(script);
script.addEventListener('load', e => {
this.renderMap()
})
} else {
this.renderMap()
}
}
renderMap() {
const coords = { lat: 41.375885, lng: 2.177813 };
const el = document.getElementById("map");
if (el) {
map = new google.maps.Map(el, {
zoom: 16,
center: {
lat: coords.lat,
lng: coords.lng
}
});
return map;
} else {
return null;
}
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleClick(event) {
const geocoder = new google.maps.Geocoder();
this.codeAddress(geocoder);
event.preventDefault();
}
//Handles Geocoding part
codeAddress(geocoder) {
var address = this.state.input;
geocoder.geocode({ address: address }, function(results, status) {
if (status === "OK") {
//'results[0].geometry.location' is the coordinates of the address you want to find
map.setCenter(results[0].geometry.location);
} else {
return null;
}
});
}
render() {
return (
<div >
<h1>ADD YOUR API KEY TO MAKE IT WORKIN</h1>
<input
id="input"
name="input"
value={this.state.input}
onChange={this.handleInputChange}
/>
<button id="submit" onClick={this.handleClick}>
{" "}
Search
</button>
<div id="map" />
</div>
);
}
}
ReactDOM.render(<GeocodingApp />, document.getElementById("app"));

Related

Live Location Tracking in React Native with Expo

Thanks in-advance. I am very new to React.
I'm trying to build an android application with React Native (Expo), PHP and MySQL, which requires live location tracking of salesmen for Admin to monitor.
Can any one please suggest me any idea on Live Location Tracking, even when the salesman's mobile screen is off/screen is locked with application running in background.
Salesman don't need any map component as they have nothing to do with their location. Just a Background Task sending their location details for the Admin, where he can track all his salesman's movement (for whole day) on their field job.
Only one application for both(Admin & Salesman), with dynamic menu based on user type which hides menu items for salesman login.
What is the standard method/process for this job ?
Is their any tutorial which i can follow ?
Any extra suggestions/points which i might be missing.
I have searched the whole internet for few days and i am having difficulty understanding the Expo documentation with very less example. Didn't found any suitable solution.
I just found a solution for my problem, sharing it for anybody who is stuck with the same problem.
Get Users Live Location (every step he takes) with React Native.
Thanks
import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import MapView from "react-native-maps";
import * as Location from "expo-location";
import * as Permissions from "expo-permissions";
import * as TaskManager from "expo-task-manager";
const LOCATION_TASK_NAME = "background-location-task";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
region: null,
error: '',
};
}
_getLocationAsync = async () => {
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
enableHighAccuracy: true,
distanceInterval: 1,
timeInterval: 5000
});
// watchPositionAsync Return Lat & Long on Position Change
this.location = await Location.watchPositionAsync(
{
enableHighAccuracy: true,
distanceInterval: 1,
timeInterval: 10000
},
newLocation => {
let { coords } = newLocation;
// console.log(coords);
let region = {
latitude: coords.latitude,
longitude: coords.longitude,
latitudeDelta: 0.045,
longitudeDelta: 0.045
};
this.setState({ region: region });
},
error => console.log(error)
);
return this.location;
};
async componentWillMount() {
// Asking for device location permission
const { status } = await Permissions.askAsync(Permissions.LOCATION);
if (status === "granted") {
this._getLocationAsync();
} else {
this.setState({ error: "Locations services needed" });
}
userId = (await AsyncStorage.getItem("userId")) || "none";
userName = (await AsyncStorage.getItem("userName")) || "none";
}
render() {
return (
<View style={styles.container}>
<MapView
initialRegion={this.state.region}
showsCompass={true}
showsUserLocation={true}
rotateEnabled={true}
ref={map => {
this.map = map;
}}
style={{ flex: 1 }}
/>
</View>
);
}
}
TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
if (error) {
console.log(error);
return;
}
if (data) {
const { locations } = data;
let lat = locations[0].coords.latitude;
let long = locations[0].coords.longitude;
userId = (await AsyncStorage.getItem("userId")) || "none";
// Storing Received Lat & Long to DB by logged In User Id
axios({
method: "POST",
url: "http://000.000.0.000/phpServer/ajax.php",
data: {
action: "saveLocation",
userId: userId,
lat,
long
}
});
// console.log("Received new locations for user = ", userId, locations);
}
});
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff"
}
});

How to render child components only in react

I am learning react, and is trying to plot a map with mapbox. I am stuck with how to render elements seperatly.
In the render part, there is a <div/> element and a component <CityWeather/> wrapped. div is a reference to map. The <Cityweather /> is an info box which is supposed to display weather information based on lat and long. The app.js file render method is
render(){
console.log(this.state);
return(
<section>
<div className="map-container" ref={x => { this.mapContainer = x;}}/>
<CityWeather lat={this.state.lat} lng={this.state.lng} />
</section>
);
The componentDidMount() is
componentDidMount() {
this.getLocation();
mapboxgl.accessToken = "";
const { lng, lat, zoom } = this.state;
const map = new mapboxgl.Map({
container: this.mapContainer,
style: "mapbox://styles/mapbox/streets-v11",
center: [lng, lat],
zoom: zoom
});
map.on("moveend", () => {
const { lng, lat } = map.getCenter();
this.setState({
lng: lng.toFixed(4),
lat: lat.toFixed(4),
zoom: map.getZoom().toFixed(2)
});
});
}
The <CityWeather /> component
class CityWeather extends Component {
constructor(props) {
super(props);
this.state = {
name: ""
};
}
componentDidMount() {
console.log(this.props); // this logs only 1 time when the page loads
fetch("api?lat=" + this.props.lat +"&lon=" +this.props.lng + "&appid=")
.then(res => res.json())
.then(data => {this.setState({ name: data.name });
}); // get name from lat and long and store it in state
}
render() {
return (
<div className="city-weather">
<p>
City | <span className="city">
{this.state.name}</span>
</p>
</div>
);
}
}
On each event the console is logging the updated latitude, longitude and zoom. Also the <CityWeather/> is also rendered for Ist time. After that the component is not rendered on state change.
You have loaded weather data in componentDidMount. It will only run the first time that the component is rendered, Not on every state change.
This is the correct code:
class CityWeather extends Component {
constructor(props) {
super(props);
this.state = {
name: ""
};
}
componentDidMount() {
this.load();
}
load(){
console.log(this.props); // this logs only 1 time when the page loads
fetch("api?lat=" + this.props.lat + "&lon=" + this.props.lng + "&appid=")
.then(res => res.json())
.then(data => {
this.setState({ name: data.name });
}); // get name from lat and long and store it in state
}
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.lat !== prevProps.lat || this.props.lng !== prevProps.lng) {
this.load();
}
}
render() {
return (
<div className="city-weather">
<p>
City | <span className="city">
{this.state.name}</span>
</p>
</div>
);
}
}
Did you add a log in the listener? I think the method was not called because you did not move. you can use timer and some mock data to test the render.

Sharing State Between Components

I want to make two components: App and Map. However when I try to make a new brand Map component and send the data from App to Map component, I cannot.
My App (default) component holds the data as a state. When I try to send this state to the Map component. It holds the data as a prop.
And of course If I don't separate them and write everything in App.js, everything works as I expected (markers shown on the map). But I want to control all states in the parent component.
Am I violating a fundamental React rule? How can I fix that?
App.js
import React, { Component } from "react";
import "./App.css";
import Map from "./Map";
class App extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
fetch(
"correct_foursquare_api_url"
)
.then(response => response.json())
.then(data =>
data.response.venues.map(place => ({
id: place.id,
name: place.name,
lat: place.location.lat,
lng: place.location.lng
}))
)
.then(locations => {
this.setState({ locations });
});
}
render() {
return (
<div className="App">
<Map locations={this.state.locations} />
</div>
)
}
}
export default App;
Map.js
import React, { Component } from "react";
/* global google */
class Map extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
this.callMap();
}
callMap() {
window.initMap = this.initMap;
loadJS(
"api_url"
);
}
// Map
initMap = () => {
const { locations, markers } = this.state;
let map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 59.4827293, lng: -83.1405355 },
zoom: 13
});
// Markers
for (var i = 0; i < locations.length; i++) {
var title = locations[i].name;
var position = new google.maps.LatLng(locations[i].lat, locations[i].lng);
var id = locations[i].id;
var marker = new google.maps.Marker({
map: map,
position: position,
title: title,
animation: google.maps.Animation.DROP,
id: id
});
markers.push(marker);
}
};
render() {
return <div id="map" />;
}
}
function loadJS(src) {
var ref = window.document.getElementsByTagName("script")[0];
var script = window.document.createElement("script");
script.src = src;
script.async = true;
ref.parentNode.insertBefore(script, ref);
}
export default Map;
You store your locations in the state of the App component, but you also have locations in state of the Map component that you use on mount.
You could instead wait with rendering the Map component until the locations request is finished, and then use the locations props in the Map component passed down from App instead.
Example
class App extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
fetch("correct_foursquare_api_url")
.then(response => response.json())
.then(data => {
const locations = data.response.venues.map(place => ({
id: place.id,
name: place.name,
lat: place.location.lat,
lng: place.location.lng
}));
this.setState({ locations });
});
}
render() {
const { locations, markers } = this.state;
if (locations.length === 0) {
return null;
}
return (
<div className="App">
<Map locations={locations} markers={markers} />
</div>
);
}
}
class Map extends Component {
// ...
initMap = () => {
const { locations, markers } = this.props;
// ...
};
// ...
}

ReactJS - get latitude on click and show it in input

Beginner here. I have a button that gets latitude and longitude using the geolocation API. I get the location fine on my console, but I'm having trouble showing them in a input box (so that I can then post the location information later). Below is my code for the component:
export class GetLocation extends Component{
constructor(){
super();
this.state = {
latitude: '',
longitude: ''
};
this.getMyLocation = this.getMyLocation.bind(this);
}
ComponentDidMount(){
this.getMyLocation();
}
getMyLocation = (e) => {
let location = null;
let latitude = null;
let longitude = null;
if (window.navigator && window.navigator.geolocation) {
location = window.navigator.geolocation
}
if (location){
location.getCurrentPosition(function (position) {
latitude = position.coords.latitude;
longitude= position.coords.longitude;
console.log(latitude);
console.log(longitude);
})
}
this.setState({latitude: latitude, longitude: longitude})
}
render(){
return(
<div>
<p>Your location is </p>
<Field name="latitude" component="input" onChange={this.getMyLocation}/>
<button type="button" onClick={this.getMyLocation}>Get Geolocation</button>
</div>
);
}
}
I'm using redux-form and this component is a part of a wizard form (in case you were wondering about the Field Component)
ComponentDidMount should be componentDidMount. I believe you have to set a value prop to your Field right?
Also, as #bennygenel mentioned, you don't need to bind getMyLocation in the constructor since you are already using arrow function (I did on my example, feel free to change it). In order to have access to this.state inside getCurrentPosition's callback, you either need to bind the success callback or make use of arrow function.
class App extends React.Component {
constructor() {
super()
this.state = {
latitude: '',
longitude: '',
}
this.getMyLocation = this.getMyLocation.bind(this)
}
componentDidMount() {
this.getMyLocation()
}
getMyLocation() {
const location = window.navigator && window.navigator.geolocation
if (location) {
location.getCurrentPosition((position) => {
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
})
}, (error) => {
this.setState({ latitude: 'err-latitude', longitude: 'err-longitude' })
})
}
}
render() {
const { latitude, longitude } = this.state
return (
<div>
<input type="text" value={latitude} />
<input type="text" value={longitude} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Like the other answers say you need to pass longitude and latitude values to the input so you can be able to show but there is another problem. You are not setting the longitude and latitude in the right place.
if (location){
location.getCurrentPosition(function (position) {
latitude = position.coords.latitude;
longitude= position.coords.longitude;
console.log(latitude);
console.log(longitude);
this.setState({
latitude: latitude,
longitude: longitude
}); // you should set state when you have the values.
}.bind(this)); // you need to bind this so you can use setState
}

How to access google.maps.Map object with react-google-maps

I have a pretty simple react application using https://github.com/tomchentw/react-google-maps but I'm having difficulty understanding how to get a reference to my current map or how to access the google.maps.Map object in a custom component.
I found this on the repo, but after reading through the posts I'm still a little confused.
I'm starting my application building off of the DirectionsRenderer example.
What I want to do next is add my own custom components for picking the starting point and using the Google Maps autocomplete API.
Yes, I know that the package has a component for that already, but I
need to do a little more than just search for a location on the map.
In order to accomplish my needs I will do something like
const autocomplete = new google.maps.places.Autocomplete(node);
autocomplete.bindTo('bounds', map);
Where node is the element I'm binding the autocomplete functionality and map is an instance of the google.maps.Map object.
My application thus far:
App.jsx
const App = ({ store }) => (
<Provider store={store}>
<div>
<Sidebar>
<StartingPoint defaultText="Choose starting point…" />
</Sidebar>
<GoogleApiWrapper />
</div>
</Provider>
);
GoogleApiWrapper
const GoogleMapHOC = compose(
withProps({
googleMapURL: 'https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=__GAPI_KEY',
loadingElement: <div style={{ height: '100vw' }} />,
containerElement: <div style={{ height: '100vh' }} />,
mapElement: <div style={{ height: '100%' }} />,
}),
withScriptjs,
withGoogleMap,
lifecycle({
componentDidMount() {
const DirectionsService = new google.maps.DirectionsService();
// make google object available to other components
this.props.onLoad(google);
DirectionsService.route({
origin: new google.maps.LatLng(41.8507300, -87.6512600),
destination: new google.maps.LatLng(41.8525800, -87.6514100),
travelMode: google.maps.TravelMode.DRIVING,
}, (result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: result,
});
} else {
console.error(`error fetching directions ${result}`);
}
});
},
}),
)(props => (
<GoogleMap
ref={props.onMapMounted}
defaultZoom={13}
defaultCenter={new google.maps.LatLng(37.771336, -122.446615)}
>
{props.directions && <DirectionsRenderer directions={props.directions} />}
</GoogleMap>
));
If I'm unable to access the google.maps.Map object outside of the wrapper I would alternatively like to access a reference to the element that contains the map so that I may instantiate a new google.maps.Map(ref_to_elem, options);
Any help would be greatly appreciated!
You can do it by React refs:
<GoogleMap ref={(map) => this._map = map} />
function someFunc () {
//using, for example as:
this._map.getCenter()
this._map.setZoom(your desired zoom);
}
import {GoogleMap, withGoogleMap} from 'react-google-maps';
import {MAP} from 'react-google-maps/lib/constants';
const MapComponent = withGoogleMap(() => (
{/*Here you have access to google.maps.Map object*/}
<GoogleMap ref={(map) => map.context[MAP]}/>
));
const Map = ({locations}) => (
<MapComponentClass
containerElement={MapComponent}
mapElement={MapComponent}
locations={locations}/>
);
export default Map;
Worth pointing out for anyone else googling this that nowdays, using react-google-maps you can simply use the useGoogleMap hook to get access to the Google maps instance
https://react-google-maps-api-docs.netlify.app/#map-instance
import React from 'react'
import { useGoogleMap } from '#react-google-maps/api'
function PanningComponent() {
const map = useGoogleMap()
React.useEffect(() => {
if (map) {
map.panTo(...)
}
}, [map])
return null
}
What I did right now in my react-redux application is I assign global variable map outside of react comnponent GoogleMap:
/*global google*/
// your imports //
var map;
class GoogleMap extends Component {
constructor(props) {
super(props);
this.state = {
// your states
};
}
// your functions
componentWillReceiveProps(nextProps) {
}
componentDidMount() {
// code
// render googlemap
map = new google.maps.Map(this.refs.map, yourMapProps);
// add click event listener to the map
map.addListener('click', function(e) {
//code
});
//viewport listener
map.addListener('idle', function(){
// code
});
}
render() {
return (
<div id="map" ref="map">
{places.map((place) => {
return(<Marker place={place} key={place.key} map={map} />);
})}
</div>
}
}
function mapDispatchToProps(dispatch) {
//code
}
export default connect(mapDispatchToProps)(GoogleMap);
Pass map as props into Child Component:
/*global google*/
import React, { Component } from 'react';
class Marker extends Component {
componentDidMount() {
this.renderMarker();
}
renderMarker() {
var { place, map } = this.props;
place.setMap(map);
}
render() {
return null;
}
}
export default Marker;
I don't know is it good practice. Bu it works. I tried to find the solution how to avoid setting Map Object as global windows.map reading all this stuff about singletons and so on. And then this came to my head. Now if I type window.map in the browser concole I get div id="map"
After thoroughly reading through the react-google-maps documentation, examples, and issues I have come to learn that the package does not support a lot of the things I will need to do for my application.
That being said, I have begun writing my own Google Maps API wrapper based off of the work done by Fullstack React. I've omitted a lot of the utilities used in the below mentioned as they can be found here or here.
That being said my solution is to wrap the google maps container in a higher order component and expose the Map Object via the window object:
App
const App = ({ store }) => (
<Provider store={store}>
<div>
<Sidebar>
<StartingPoint />
{/* TODO */}
</Sidebar>
<GoogleMap />
</div>
</Provider>
);
containers/GoogleMap/wrapper.jsx Google Map Higher Order Component wraps GoogleMap Container
const defaultCreateCache = (options) => {
const opts = options || {};
const apiKey = opts.apiKey;
const libraries = opts.libraries || ['places'];
const version = opts.version || '3.24';
const language = opts.language || 'en';
return ScriptCache({
google: GoogleApi({
apiKey,
language,
libraries,
version,
}),
});
};
const wrapper = options => (WrappedComponent) => {
const createCache = options.createCache || defaultCreateCache;
class Wrapper extends Component {
constructor(props, context) {
super(props, context);
this.scriptCache = createCache(options);
this.scriptCache.google.onLoad(this.onLoad.bind(this));
this.state = {
loaded: false,
google: null,
};
}
onLoad() {
this.GAPI = window.google;
this.setState({ loaded: true, google: this.GAPI });
}
render() {
const props = Object.assign({}, this.props, {
loaded: this.state.loaded,
google: window.google,
});
const mapRef = (el) => { this.map = el; };
return (
<div>
<WrappedComponent {...props} />
<div ref={mapRef} />
</div>
);
}
}
Wrapper.propTypes = {
dispatchGoogleAPI: PropTypes.func,
};
Wrapper.defaultProps = {
dispatchGoogleAPI: null,
};
return Wrapper;
};
export default wrapper;
containers/GoogleMap/index.jsx Google Map Container
class Container extends Component {
constructor(props) {
super(props);
this.loadMap = this.loadMap.bind(this);
this.calcRoute = this.calcRoute.bind(this);
}
componentDidUpdate() {
const { origin, destination, route } = this.props;
this.calcRoute(origin, destination);
}
loadMap(node) {
if (this.props && this.props.google) {
const { google } = this.props;
// instantiate Direction Service
this.directionsService = new google.maps.DirectionsService();
this.directionsDisplay = new google.maps.DirectionsRenderer({
suppressMarkers: true,
});
const zoom = 13;
const mapTypeId = google.maps.MapTypeId.ROADMAP;
const lat = 37.776443;
const lng = -122.451978;
const center = new google.maps.LatLng(lat, lng);
const mapConfig = Object.assign({}, {
center,
zoom,
mapTypeId,
});
this.map = new google.maps.Map(node, mapConfig);
this.directionsDisplay.setMap(this.map);
// make the map instance available to other components
window.map = this.map
}
}
calcRoute(origin, destination) {
const { google, route } = this.props;
if (!origin && !destination && !route) return;
const waypts = [];
waypts.push({
location: new google.maps.LatLng(37.415284, -122.076899),
stopover: true,
});
const start = new google.maps.LatLng(origin.lat, origin.lng);
const end = new google.maps.LatLng(destination.lat, destination.lng);
this.createMarker(end);
const request = {
origin: start,
destination: end,
waypoints: waypts,
optimizeWaypoints: true,
travelMode: google.maps.DirectionsTravelMode.DRIVING,
};
this.directionsService.route(request, (response, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.directionsDisplay.setDirections(response);
const route = response.routes[0];
console.log(route);
}
});
this.props.calculateRoute(false);
}
createMarker(latlng) {
const { google } = this.props;
const marker = new google.maps.Marker({
position: latlng,
map: this.map,
});
}
render() {
return (
<div>
<GoogleMapView loaded={this.props.loaded} loadMap={this.loadMap} />
</div>
);
}
}
const GoogleMapContainer = wrapper({
apiKey: ('YOUR_API_KEY'),
version: '3', // 3.*
libraries: ['places'],
})(Container);
const mapStateToProps = state => ({
origin: state.Trip.origin,
destination: state.Trip.destination,
route: state.Trip.route,
});
const mapDispatchToProps = dispatch => ({
dispatchGoogleMap: (map) => {
dispatch(googleMap(map));
},
calculateRoute: (route) => {
dispatch(tripCalculation(route));
},
});
const GoogleMap = connect(mapStateToProps, mapDispatchToProps)(GoogleMapContainer);
export default GoogleMap;

Categories

Resources