Im new to react and building an app to better understand it. Im currently using the Google maps api to render a map and want to render multiple markers to the map.
Im having to use a geocode api to get the lat and longitude for each of my locations so I can display them on the map as markers. Currently my code isn't displaying anything to the screen and I think its because the map is trying to render the markers with an empty state variable before the fetch calls are completed.
I have tried to search for an answer but cant find anything that is similar to what Im trying to do.
Here is my code
import React, { useContext, useEffect, useState } from 'react';
import { GoogleMap, useLoadScript, Marker } from "#react-google-maps/api"
import Settings from "../Settings"
import mapSyles from "./MapStyles"
import { LogContext } from '../divelog/DiveLogProvider';
export const MapRender =(props) => {
const {diveLogs} = useContext(LogContext)
const [latLong, setLatLong] = useState()
//get the logs location and run that through api to get lat and long
useEffect(()=>{
let latLongs = []
diveLogs.map(dl =>{
return fetch(`http://api.positionstack.com/v1/forward?access_key=MYKEY&query=${dl.location}&limit=1
`)
.then(res => res.json())
.then(parsedRes => {
latLongs.push(parsedRes.data[0])
})
})
setLatLong(latLongs)
console.log(latLong)
},[diveLogs])
//here I want to map through the state variable and display all of the markers on the map
return (
<div>
<GoogleMap
mapContainerStyle={mapContainerStyle}
options={options}
zoom={1}
center={center}
>
{
//this is where I map through the state variable
latLong.map(l =>(
<Marker key={l.lat}
position ={{lat: l.latitude, lng: l.longitude}}
/>
))
}
</GoogleMap>
</div>
)
}
you can update the state right inside your response . like this
diveLogs.map(dl =>{
return fetch(`http://api.positionstack.com/v1/forward?access_key=MYKEY&query=${dl.location}&limit=1
`)
.then(res => res.json())
.then(parsedRes => {
setLatLong( prevLatLongs => [...prevLatLongs ,parsedRes.data[0]] )
})
})
Yo need update the state when the fetch result is ready:
useEffect(()=>{
diveLogs.map(dl =>{
return fetch(`http://api.positionstack.com/v1/forward?access_key=MYKEY&query=${dl.location}&limit=1
`)
.then(res => res.json())
.then(parsedRes => {
setLatLong((prev)=> [...prev, parsedRes.data[0]])
})
})
},[diveLogs])
Related
I don't understand why my "console.log(champion)" return nothing ...
Someone can explain me why the asynchrone function don't work ? Isn't setCahmp supposed to change the value of "champions"?
I guess it because axios take sometime to search datas... I don't know how I could fix it.
And then I would like to map "champion" but its an object, how I could do that ?
Thans you
import React, { useEffect, useState } from "react";
import axios from "axios";
const Champs = () => {
const [champions, SetChampions] = useState([]);
useEffect(() => {
axios
.get(
"http://ddragon.leagueoflegends.com/cdn/12.5.1/data/en_US/champion.json"
)
.then((res) => {
SetChampions(res.data.data);
console.log(res.data.data);
})
.then(
console.log(champions)
);
}, []);
return (
<div className="champs">
{/* {champions.map((champ) => {
return <p> {champ.id}</p>;
})} */}
</div>
);
};
export default Champs;
In your API response response.data.data is not an array of objects, it's nested objects and you are initializing the champions as an array. So, setChampions can't assign an object to an array.
Also, you can't use the map function to loop an object. You can use Object.keys to map the response.
You shouldn't do a double "then" on your code. If you want to know when the state champions is set you should use a second useEffect with "champions" in param :
useEffect(() => {
axios
.get(
"http://ddragon.leagueoflegends.com/cdn/12.5.1/data/en_US/champion.json"
)
.then((res) => {
SetChampions(res.data.data);
console.log(res.data.data);
});
}, []);
useEffect(() => {
console.log(champions)
}, [champions]);
If you want to map an object you should do this :
<div className="champs">
{Object.keys(champions).map((key) => {
const champ = champions[key]
return <p> {champ.id}</p>;
})}
</div>
Object.keys will return an array of key of your object, so you can map it. And to access to the value you can simply use the key like this : const champ = champions[key]
Hoping that can help you in your research
It could be that console.log(champion) isn't working because it's getting called before SetChampion is completed. You don't need the 2nd .then() call to check on champion. To make sure champion is getting set, you could make a useEffect that is called whenever champion gets set, like so:
useEffect(() => {
console.log(champion);
}, [champion])
This will get called when champion is initially set to [] with useState, and then when you set it with SetChampions().
I'm using the react-google-maps/api library, and I have an application where I need the user to edit a Polyline.
The problem I'm having is grabbing the path of the polyline after the user has finished editing.
If I use native react components, the path returned on the props from the polyline is the original path of the line - not the one edited by the user.
The code below is a cutdown version of where I'm trying to get the path of the line from the react component. If you try it and edit the line, the return array is the original path. I've seen some examples using the getPath() method, but I just can't seem to get this to work on the React component (ie polylineRef.current.getPath() returns a no function error.
How should I be getting the path information of the edited line?
import React, { Fragment, useRef } from "react";
import { GoogleMap, Polyline, useLoadScript } from "#react-google-maps/api";
const MapTest = (props) => {
const polylineRef = useRef();
const mapRef = useRef();
const mapContainerStyle = {
width: "80vw",
height: "80vh",
};
const showPath = () => {
console.log(polylineRef.current.props.path); //What should be here to show the edited path if its possible to access?
};
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.REACT_APP_GOOGLE_KEY,
});
const centre = { lat: 51.999889, lng: -0.98807 };
if (loadError) return "Error loading Google Map";
if (!isLoaded) return "Loading Maps....";
console.log(polylineRef.current.props.path);
return (
<Fragment>
<GoogleMap
mapContainerStyle={mapContainerStyle}
ref={mapRef}
zoom={10}
center={centre}
>
<Polyline
ref={polylineRef}
key={1}
path={[
{ lat: 51.9298274729133, lng: -1.0446431525421085 },
{ lat: 51.98483618577529, lng: -1.2423970587921085 },
]}
options={{ editable: true, strokeColor: "#ff0000" }}
/>
</GoogleMap>
<button
onClick={(event) => {
showPath(event);
}}
>
Show Path in Console
</button>
</Fragment>
);
};
export default MapTest;
If I use the native google API, then I can see the updated path, but I can't get a reference to the map created by the map to place the polyline onto.
If I can't access the edited path through the react component, how should I provide a reference to the google maps native API, so when I do
polyline = new google.maps.Polyline(//polyline options)
polyline.setMap(map) //Where do I get the handle for this map to put it on the map above?
/*I've tried using mapRef.current (not a map instance) and
mapRef.current.getInstance() - this makes the original map disappear, for reasons I don't understand*/
When I build this using the native API, I can access the edited path using the getPath() method, but I can't render this polyline on the component rendered above.
Other than building the map out of the native API I'm struggling to do this at the moment - but the benefits of the ease of rendering of React make me want to continue down this path for a while longer - is anyone able to help please?
I think this is what you are trying to achieve:
https://codesandbox.io/s/snowy-night-ony59?file=/src/App.js
My answer is based on:
https://codesandbox.io/s/reactgooglemapsapi-editing-a-polygon-popr2?file=/src/index.js:2601-2845
which I found by googling: react-google-maps-api editable polygon
Basically just copying and pasting the code referred by Daniele Cordano
import React, { useState, useRef, useCallback } from "react";
import ReactDOM from "react-dom";
import { LoadScript, GoogleMap, Polygon } from "#react-google-maps/api";
import "./styles.css";
// This example presents a way to handle editing a Polygon
// The objective is to get the new path on every editing event :
// - on dragging the whole Polygon
// - on moving one of the existing points (vertex)
// - on adding a new point by dragging an edge point (midway between two vertices)
// We achieve it by defining refs for the google maps API Polygon instances and listeners with `useRef`
// Then we bind those refs to the currents instances with the help of `onLoad`
// Then we get the new path value with the `onEdit` `useCallback` and pass it to `setPath`
// Finally we clean up the refs with `onUnmount`
function App() {
// Store Polygon path in state
const [path, setPath] = useState([
{ lat: 52.52549080781086, lng: 13.398118538856465 },
{ lat: 52.48578559055679, lng: 13.36653284549709 },
{ lat: 52.48871246221608, lng: 13.44618372440334 }
]);
// Define refs for Polygon instance and listeners
const polygonRef = useRef(null);
const listenersRef = useRef([]);
// Call setPath with new edited path
const onEdit = useCallback(() => {
if (polygonRef.current) {
const nextPath = polygonRef.current
.getPath()
.getArray()
.map(latLng => {
return { lat: latLng.lat(), lng: latLng.lng() };
});
setPath(nextPath);
}
}, [setPath]);
// Bind refs to current Polygon and listeners
const onLoad = useCallback(
polygon => {
polygonRef.current = polygon;
const path = polygon.getPath();
listenersRef.current.push(
path.addListener("set_at", onEdit),
path.addListener("insert_at", onEdit),
path.addListener("remove_at", onEdit)
);
},
[onEdit]
);
// Clean up refs
const onUnmount = useCallback(() => {
listenersRef.current.forEach(lis => lis.remove());
polygonRef.current = null;
}, []);
console.log("The path state is", path);
return (
<div className="App">
<LoadScript
id="script-loader"
googleMapsApiKey=""
language="en"
region="us"
>
<GoogleMap
mapContainerClassName="App-map"
center={{ lat: 52.52047739093263, lng: 13.36653284549709 }}
zoom={12}
version="weekly"
on
>
<Polygon
// Make the Polygon editable / draggable
editable
draggable
path={path}
// Event used when manipulating and adding points
onMouseUp={onEdit}
// Event used when dragging the whole Polygon
onDragEnd={onEdit}
onLoad={onLoad}
onUnmount={onUnmount}
/>
</GoogleMap>
</LoadScript>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
For some reason, the useRef hook doesn't accept the function getPath/getPaths() with the typescript error : TS2339: Property 'getPath' does not exist on type 'MutableRefObject'
const handleNewPolygonPath = useCallback(() => {
const newPath = polygonRef.getPath();
dispatch(setNewPolygonPath(newPath));
console.log(newPolygonPath);
}, [dispatch, newPolygonPath]);
Example of issue
import firebase from 'firebase/app';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import { OpenStreetMapProvider } from 'leaflet-geosearch'
import SearchControl from "./SearchControl";
const userId = sessionStorage.getItem('uid')
const fullNames = []
const provider = new OpenStreetMapProvider()
var vendors = []
const VendorMap = (props) => {
firebase.firestore().collection("vendors")
.where('belongsTo', '==', userId)
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
if(fullNames.includes(doc.data().fullName) == false) {
fullNames.push(doc.data().fullName)
provider.search({query: doc.data().address}).then(function (result) {
vendors.push([doc.data(), [result[0].y, result[0].x]])
}).catch((error) => {
console.log("Error getting collection: ", error)
})
}
})
}).catch((error) => {
console.log("Error getting collection: ", error)
})
return (
<MapContainer center={[51.505, -0.09]} zoom={3} scrollWheelZoom={true}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<SearchControl provider={provider} showMarker={true} showPopup={true} popupFormat={({ query, result }) => result.label} maxMarkers={3} retainZoomLevel={false} animateZoom={true} autoClose={false} searchLabel={"Address to search for..."} keepResult={true} />
{vendors.map((vendor) =>
<Marker key={vendor[0].fullName} position={[vendor[1][0], vendor[1][1]]}>
<Popup>
<span>{vendor[0].fullName}</span>
</Popup>
</Marker>
)}
</MapContainer>
)
}
export default VendorMap
The above link is a video of my issue (its only 10 seconds). React is not immediately loading all of the map markers every time it loads. Sometimes all of the markers load, sometimes only 1 does and I have to click on the navigation bar link for the rest to load.
I am not understanding what the issue could be since I am adding all of the markers one after another to the map. React also doesn't seem to be loading the information from firestore correctly as when I log into the website, I have to completely refresh the website before information begins to load.
If anyone has any insight into what I may be doing wrong and how to fix this, it would be great! I am really new to NodeJS and ReactJS so excuse any common issues on my end.
I am not able to reproduce your issue but the first two things you need to change is:
Place the async operation inside a useEffect hook to load only once when the page loads.
use a useState hook to store the data received from the async operation inside VendorMap component because storing the data in a variable outside the functional component like you do might have unexpected results as React does not know when to update the DOM and compare the old with the new values.
As a result create a local variable using a useState hook
const [vendors, setVendors] = useState([]);
useEffect(() => {
firebase.firestore().collection("vendors")
.where('belongsTo', '==', userId)
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
if(fullNames.includes(doc.data().fullName) == false) {
fullNames.push(doc.data().fullName)
provider.search({query: doc.data().address}).then(function (result) {
setVendords(yourVariable) // update the vendors array
}).catch((error) => {
console.log("Error getting collection: ", error)
})
}
})
}).catch((error) => {
console.log("Error getting collection: ", error)
})
}, []);
Inside setVendors update vendors variable. I am not sure regarding the data you receive that is the reason I did not put the exact value
If you update fullNames like vendors follow the same pattern as for vendors variable.
This question already has answers here:
How to implement Google Places API in React JS?
(2 answers)
Closed 2 years ago.
I want to use Google Maps and Places API in my React application for users to be able get hospitals in their area on the map.
I am using both #react-google-maps/api and use-places-autocomplete npm packages to interface with the API to display the map and places. I am using the useLoadScript hook to load in my API key and also declare libraries I will be using.
In this case, just as I have enabled Places and Maps JavaScript APIs, I supplied places in the libraries array.
I have tried adding the below script to the index.html file:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places"></script>
but I get the error: You are loading the map multiple times.
Here's a snippet of my code where I use the useLoadScript hook from the #react-google-maps/api
Please how do I resolve this issue?
If you are using useLoadScript of the #react-google-maps/api library and use-places-autocomplete you need to supply the ['places'] as a value of your library to use the Places Autocomplete in your code. Please note that this useLoadScript will load the Google Maps JavaScript Script tag in your code and you don't need to add the script tag in the html file anymore.
From what I understand, your use case where the user will select a place from the autocomplete dropdown and the nearest hospital should be returned in the map.
To achieve this, you need to have this flow:
Use use-places-autocomplete to provide place suggestion when searching for a place. This library have a GetGeocode and a GetLatLng that you can use to get the coordinates of the chosen place.
Once you have the coordinates, you can use Places API Nearby Search, use the keyword hospital in your request and define a radius. This will search a list of hospital within the defined radius with the chosen place as the center.
You can loop through the list of result and pin each coordinates as markers in your map.
Here;s the sample code and a code snippet below. Make sure to add your API Key:
Index.js
import React from "react";
import { render } from "react-dom";
import Map from "./Map";
render(<Map />, document.getElementById("root"));
Map.js
/*global google*/
import React from "react";
import { GoogleMap, useLoadScript } from "#react-google-maps/api";
import Search from "./Search";
let service;
const libraries = ["places"];
const mapContainerStyle = {
height: "100vh",
width: "100vw"
};
const options = {
disableDefaultUI: true,
zoomControl: true
};
const center = {
lat: 43.6532,
lng: -79.3832
};
export default function App() {
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: "YOUR_API_KEY",
libraries
});
const mapRef = React.useRef();
const onMapLoad = React.useCallback(map => {
mapRef.current = map;
}, []);
const panTo = React.useCallback(({ lat, lng }) => {
mapRef.current.panTo({ lat, lng });
mapRef.current.setZoom(12);
let map = mapRef.current;
let request = {
location: { lat, lng },
radius: "500",
type: ["hospital"]
};
service = new google.maps.places.PlacesService(mapRef.current);
service.nearbySearch(request, callback);
function callback(results, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
for (let i = 0; i < results.length; i++) {
let place = results[i];
new google.maps.Marker({
position: place.geometry.location,
map
});
}
}
}
}, []);
return (
<div>
<Search panTo={panTo} />
<GoogleMap
id="map"
mapContainerStyle={mapContainerStyle}
zoom={8}
center={center}
options={options}
onLoad={onMapLoad}
/>
</div>
);
}
Search.js
import React from "react";
import usePlacesAutocomplete, {
getGeocode,
getLatLng
} from "use-places-autocomplete";
export default function Search({ panTo }) {
const {
ready,
value,
suggestions: { status, data },
setValue,
clearSuggestions
} = usePlacesAutocomplete({
requestOptions: {
/* Define search scope here */
},
debounce: 300
});
const handleInput = e => {
// Update the keyword of the input element
setValue(e.target.value);
};
const handleSelect = ({ description }) => () => {
// When user selects a place, we can replace the keyword without request data from API
// by setting the second parameter to "false"
setValue(description, false);
clearSuggestions();
// Get latitude and longitude via utility functions
getGeocode({ address: description })
.then(results => getLatLng(results[0]))
.then(({ lat, lng }) => {
panTo({ lat, lng });
})
.catch(error => {
console.log("😱 Error: ", error);
});
};
const renderSuggestions = () =>
data.map(suggestion => {
const {
place_id,
structured_formatting: { main_text, secondary_text }
} = suggestion;
return (
<li key={place_id} onClick={handleSelect(suggestion)}>
<strong>{main_text}</strong> <small>{secondary_text}</small>
</li>
);
});
return (
<div>
<input
value={value}
onChange={handleInput}
disabled={!ready}
placeholder="Where are you going?"
/>
{/* We can use the "status" to decide whether we should display the dropdown or not */}
{status === "OK" && <ul>{renderSuggestions()}</ul>}
</div>
);
}
I wanna display the data that I have in my state in a map I managed to get both latitude and longitude in my state the problem is whenever try to map trough the state I always get it's not a function error here's parts of my code and the data that I have in the state:
const [countriesData, setCountriesData] = useState({});
useEffect(() => {
const fetchAPI = async () => {
setCountriesData(await fetchDataCountries());
};
fetchAPI();
}, [setCountriesData]);
console.log(countriesData);
and mapping through it like this:
{countriesData.map((data)=>(
<Marker latitude={data.countriesData.latitude}
longitude={data.countriesData.longitude}/>
))}
the fetch api function:
export const fetchDataCountries = async () => {
try {
const data = await axios.get(url);
const newData = data.data;
const modfiedData = newData.map((countryData) => {
return countryData.coordinates;
});
return modfiedData;
} catch (error) {
console.log(error);
}
};
For your map to work you need to convert yourstate to an array, now it is an object.
After that, it could look something like this:
{countriesData.map((country) => (
<Marker
latitude={country.latitude}
longitude={country.longitude}
/>
))}
or
{countriesData.map(({ latitude, longitude }) => (
<Marker
latitude={latitude}
longitude={longitude}
/>
))}
But for a more accurate answer, it would be nice to have an example of the contents of your state.
You're initializing the countriesData with an empty object, then you're fetching a API data.
Assuming the response is an array of objects like this:
[
{ latitude: 38.8451, longitude: -37.0294 }
{ ... // other coordinates }
]
Change the initial state to be an empty array like this:
const [countriesData, setCountriesData] = useState([]);
Also, update this block:
{countriesData.map((data) => <Marker latitude={data.latitude} longitude={data.longitude} />)}
Explanation: the map() method only works with Arrays [] and not with Objects {}, therefore when the component was mounted/initialized for the first time, the map method was executed against an object, thus throwing the map is not a function error.