Wait until useState object has value - javascript

I've made a custom hook, which can be used to fetch users location and find nearest marker on map based on that information.
Right now it works, but first object it returns is useStates default value. Response looks like this:
{
coordinates: { lat: '', lng: '' },
loaded: false
}
After returning object first time, it starts to work like it should:
{
coordinates: { lat: 45.024335, lng: 19.277089 },
loaded: true
}
So basically I don't want that the hook sends empty objects, with no values. How can I fix this?
const useGeoLocation = (markers) => {
const [location, setLocation] = useState({
loaded: false,
coordinates: { lat: "", lng: "" },
});
const onSuccess = (location) => {
// User location succesfully fetched, converting data format.
const targetPoint = turf.point([
location.coords.latitude,
location.coords.longitude,
]);
// Fetching data from markers parameter and converting data format.
var arr = [];
markers.map((marker) => arr.push(turf.point(marker.location)));
var points2 = turf.featureCollection(arr);
// Calculating which marker is nearest to targetPoint.
var nearest = turf.nearestPoint(targetPoint, points2);
// Setting location of nearest marker to usestate.
setLocation({
loaded: true,
coordinates: {
lat: nearest.geometry.coordinates[0],
lng: nearest.geometry.coordinates[1],
},
});
};
const onError = () => {
setLocation({
loaded: true,
coordinates: {
lat: 65.024335,
lng: 27.277089,
},
});
};
useEffect(() => {
if (markers) {
if (!("geolocation" in navigator)) {
onError();
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}
}, [markers]);
return location;
};

Instead of just returning the location object as a whole, you could split the return into an object with values for the data, a loading state, an error message if needs be. For example:
const useGeoLocation = (markers) => {
const [location, setLocation] = useState(undefined);
const [error, setError] = useState("");
const onSuccess = (location) => {
// Rest of the success handler...
setLocation({
coordinates: {
lat: nearest.geometry.coordinates[0],
lng: nearest.geometry.coordinates[1],
},
});
};
const onError = () => {
setLocation({
coordinates: {
lat: 65.024335,
lng: 27.277089,
},
});
setError("Some error message");
};
useEffect(() => {
if (markers) {
if (!("geolocation" in navigator)) {
onError();
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}
}, [markers]);
return {
data: location,
isLoading: !location && !error,
error: error,
};
};
Then when calling the hook you can do:
const {data, isLoading, error} = useGeoLocation();
and check for the value of data, or that isLoading and error are false, before doing anything.

Related

Passing parameter to custom hook

There is lot of documentation about the hooks, but I don't seem to understand them well enough.
Code
const App = () => {
const [markers, setMarkers] = useState(null);
const fetchMarkers = async () => {
const res = await axios.get(`${process.env.REACT_APP_BASE_URL}/markers`);
setMarkers(res.data);
};
useEffect(() => {
fetchMarkers();
}, []);
useInterval(() => {
fetchMarkers();
}, 10000);
}
I've made a custom hook which returns an object, I can call it like this:
const location = useGeoLocation();
I would like to add markers as parameter to useGeoLocation hook. This can be achieved easily just by calling the function on code and adding markers.
Objective
My goal is to make sure that markers contains some value before calling useGeoLocation. Right now, geoLocation hooks parameter returns value of null, because application didn't have time to fetchMarkers and update the state.
I've tried
My first thought was to do something like this:
if (markers) {
const location = useGeoLocation(markers);
}
But of course, like stated in rules of hooks, hooks can't be used conditionally.
UPDATED 10.1.2022 - useGeoLocation hook
Here I'm fetching users location. On success, I will compare users location to an array of markers & find the closest one to user.
On error, I will return only a predefined location.
This doesn't work because onSuccess is executed before there is any data in markers(null). I'm not sure how to use if - else statement here, because I'm also using useEffect & useState.
Can someone help?
const useGeoLocation = (markers) => {
const [location, setLocation] = useState({
loaded: false,
coordinates: { lat: "", lng: "" },
});
const onSuccess = (location) => {
// User location succesfully fetched, converting data format.
const targetPoint = turf.point([
location.coords.latitude,
location.coords.longitude,
]);
// Fetching data from markers parameter and converting data format.
var arr = [];
markers.map((marker) => arr.push(turf.point(marker.location)));
var points2 = turf.featureCollection(arr);
// Calculating which marker is nearest to targetPoint.
var nearest = turf.nearestPoint(targetPoint, points2);
// Setting location of nearest marker to usestate.
setLocation({
loaded: true,
coordinates: {
lat: nearest.geometry.coordinates[0],
lng: nearest.geometry.coordinates[1],
},
});
};
const onError = () => {
setLocation({
loaded: true,
coordinates: {
lat: 65.024335,
lng: 27.277089,
},
});
};
useEffect(() => {
if (!("geolocation" in navigator)) {
onError();
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}, []);
return location;
};
Add markers in the dependency array of useEffect hook of useGeoLocation and call navigator.geolocation.getCurrentPosition(onSuccess, onError) if the markers are available and in the else part you can call setLocation().
const useGeoLocation = (markers) => {
const [location, setLocation] = useState({
loaded: false,
coordinates: { lat: "", lng: "" },
});
useEffect(() => {
if (markers) {
if (!("geolocation" in navigator)) {
onError();
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
} else {
setLocation();
}
}, [markers]);
return location;
};

Query esri search widget

this is the sample search widget in arcGIS, I want to override the search function, that the only thing that can be searched is the data in my query and it will appear just like in the picture
const [searchData, setSearchData] = useState(null);
const fetchSearchLocation = async (name) => {
const { data } = await client.query({
query: SEARCH_LOCATION,
variables: {
name: name
},
})
setSearchData(data);
return data;
};
useEffect(() => {
location();
}, []);
const location = () => {
if (mapDiv.current) {
esriConfig.apiKey = "sample api key";
const map = new Map({
basemap: 'arcgis-light-gray',
});
const view = new MapView({
center: [123.5504, 12.3574], // Longitude, latitude
container: mapDiv.current,
map: map,
zoom: 2, // Zoom level
});
const searchWidget = new Search({
view: view,
sources: [customSearchSource],
includeDefaultSources: false
});
view.ui.add(searchWidget, { position: "top-left", index: 0 });
view
.when((r) => {})
.then(() => {
mapDiv.current = view;
fetchData();
});
}
};
const searchDataLocation = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("searchData: ", searchData)
resolve(searchData); //test_data = Your data.
}, 1000);
});
const customSearchSource = new SearchSource({
placeholder: 'Search',
getSuggestions: (params) => {
fetchSearchLocation(params.suggestTerm)
console.log("searchData2: ", searchData)
return searchDataLocation.then((data) => {
console.log("datas: ", data) // the result is null.
})
....
resource
https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Search.html
How to put queries in ArcGIS esri search?
note: this is edited question

React won't loop through state in render

I'm fairly new to React and I have run into a problem. I need to gather data from an API and then use the data to create Polygons with Google Maps. However, I'm experiencing problems accessing it.
When I log this.state.coordinates:
Console.log result of this.state.coordinates
However, when I try to log this.state.coordinates[0] it shows undefined. My guess is that the order in which the data binds is wrong, but I can't seem to find the solution. Here is my code:
constructor(props) {
super(props);
this.state = {
coordinates: [{}],
loading: 'initial'
};
}
componentDidMount() {
this.setState({
loading: 'true'
})
this.getData()
}
getData = async () => {
let coordinates = []
let res = await axios.get('https://smartcity-parking-api.herokuapp.com/sectors')
const sectors = await res.data.data
sectors.map(async (sector, i) => {
let res = await axios.get(sector.self_links.detail)
const coordinateArray = await res.data.data.coordinates
coordinates[i] = {
latlng: [],
id: res.data.data.sector_data.sector_id
}
coordinateArray.map(coordinate => {
let latlng = {
lat: coordinate.latitude,
lng: coordinate.longitude
}
coordinates[i].latlng.push(latlng)
})
coordinates[i].latlng.push({
lat: coordinateArray[0].latitude,
lng: coordinateArray[0].longitude
})
})
this.setState({
coordinates: coordinates,
loading: 'false'
})
}
render() {
let { coordinates, loading } = this.state
if (loading === 'initial') {
return <h2>Intializing...</h2>;
}
if (loading === 'true') {
return <h2>Loading...</h2>;
}
if (loading === 'false') {
return (
//Google maps here
);
}
I have tried making a loader, but it doesn't seem to be working. Any help would be very appreciated!
Why not set coordinates to null initially in your constructor and then have your conditional be
(loading === 'false' && coordinates)
That may solve your issue.
Change getData function to this.
getData = async () => {
let coordinates = []
let res = await axios.get('https://smartcity-parking-api.herokuapp.com/sectors')
const sectors = res.data.data
const sectorsLinks = await Promise.all(sectors.map(sector => axios.get(sector.self_links.detail)))
sectorsLinks.map((sector, i) => {
const coordinateArray = sector.data.data.coordinates;
coordinates[i] = {
latlng: [],
id: sector.data.data.sector_data.sector_id
}
coordinateArray.map(coordinate => {
let latlng = {
lat: coordinate.latitude,
lng: coordinate.longitude
}
coordinates[i].latlng.push(latlng)
})
coordinates[i].latlng.push({
lat: coordinateArray[0].latitude,
lng: coordinateArray[0].longitude
})
});
console.log(coordinates);
this.setState({
coordinates: coordinates,
loading: 'false'
})
};

Firesbase firestore not working + Vuejs

I want to store my geopoints to firestore database but it wont work.
I created an array called acctPosition to store there the geopoints inside,
but in firebase i can't see the data, it looks like that nothing is stored.
How can I store the geopoints correctly?
The output:
The Geoloaction component:
data () {
coords: {
latitude: null,
longitude: null
},
acctPosition: []
},
...
mounted () {
var self = this;
// Try HTML5 geolocation.
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var pos = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
console.log(pos)
var acctPos = self.acctPosition.push(pos)
console.log(acctPos)
}
} else {
// Browser doesn't support Geolocation
}
let docId = `${this.currentUser.uid}`
// store geoData to currentUser in firebase firestore
fb.usersCollection.doc(docId).set({
acctPosition: this.acctPos
}).then(ref => {
console.log('work')
}).catch(err => {
console.log(err)
})
}
Javascript is asynchronous, so fb.usersCollection.doc(docId).set() is going to be executed before getCurrentPosition is finished.
You can push to firebase after getCurrentPosition is done:
mounted () {
...
var self = this;
// Try HTML5 geolocation.
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var pos = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
console.log(pos)
map.setCenter(pos);
var acctPos = self.acctPosition.push(pos)
console.log(acctPos)
let docId = `${self.currentUser.uid}`
// store geoData to currentUser in firebase firestore
fb.usersCollection.doc(docId).set({
acctPosition: self.acctPosition
}).then(ref => {
console.log('work')
}).catch(err => {
console.log(err)
})
}
} else {
// Browser doesn't support Geolocation
}
}

Watch Position Ionic 3

Hi guys I need help in how to watch position in google maps with Ionic 3, I would like that upload position in real time in the firebase
In my home.ts I have the following code :
getMyPosition() {
this.geolocation.getCurrentPosition().then((result) => {
this.positionTruck = new google.maps.LatLng(result.coords.latitude, result.coords.longitude);
const mapOptions = {
zoom: 18,
center: this.positionTruck,
disableDefaultUI: true
}
this.map = new google.maps.Map(document.getElementById('map'), mapOptions);
this.truckMarker(this.positionTruck);
let watch = this.geolocation.watchPosition();
watch.subscribe((data) => {
let truck = { latitude: data.coords.latitude, longitude: data.coords.longitude };
this.truckService.updateGeolocation(this.truck.key, truck);
console.log(data.coords)
});
}).catch((error) => {
console.log('Erro ao tentar pegar sua localização ', error);
})
}
And now I have this code in service to update truck.service:
update(key: string, truck: any) {
return new Promise((resolve, reject) => {
this.db.list(this.PATH)
.update(key, (truck))
.then(() => resolve())
.catch((e) => reject())
})
}
this way when I test on the device, the home page giving refresh in page, why ?
Is this form correctly the update position ? help please.
import : import { Geolocation, Geoposition } from '#ionic-native/geolocation';
and declare :
public lat: number = 0;
public lng: number = 0;
and add in your code
let options = {
frequency: 3000,
enableHighAccuracy: true
};
this.watch = this.geolocation.watchPosition(options).filter((p: any) => p.code === undefined).subscribe((position: Geoposition) => {
console.log(position.coords.latitude + ' ++++++++++ ' + position.coords.longitude);
// Run update inside of Angular's zone
this.zone.run(() => {
this.lat = position.coords.latitude;
this.lng = position.coords.longitude;
);
});
});

Categories