Convert from class-based component to functional-based component - reactjs

I am trying to use the react-native-maps-directions example found in https://www.npmjs.com/package/react-native-maps-directions on my project.
Can someone help me convert this code from class base component to functional base component.
import React, { Component } from 'react';
import { Dimensions, StyleSheet } from 'react-native';
import MapView from 'react-native-maps';
import MapViewDirections from 'react-native-maps-directions';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 37.771707;
const LONGITUDE = -122.4053769;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const GOOGLE_MAPS_APIKEY = '…';
class Example extends Component {
constructor(props) {
super(props);
// AirBnB's Office, and Apple Park
this.state = {
coordinates: [
{
latitude: 37.3317876,
longitude: -122.0054812,
},
{
latitude: 37.771707,
longitude: -122.4053769,
},
],
};
this.mapView = null;
}
onMapPress = (e) => {
this.setState({
coordinates: [
...this.state.coordinates,
e.nativeEvent.coordinate,
],
});
}
render() {
return (
<MapView
initialRegion={{
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}}
style={StyleSheet.absoluteFill}
ref={c => this.mapView = c}
onPress={this.onMapPress}
>
{this.state.coordinates.map((coordinate, index) =>
<MapView.Marker key={`coordinate_${index}`} coordinate={coordinate} />
)}
{(this.state.coordinates.length >= 2) && (
<MapViewDirections
origin={this.state.coordinates[0]}
waypoints={ (this.state.coordinates.length > 2) ? this.state.coordinates.slice(1, -1): undefined}
destination={this.state.coordinates[this.state.coordinates.length-1]}
apikey={GOOGLE_MAPS_APIKEY}
strokeWidth={3}
strokeColor="hotpink"
optimizeWaypoints={true}
onStart={(params) => {
console.log(`Started routing between "${params.origin}" and "${params.destination}"`);
}}
onReady={result => {
console.log(`Distance: ${result.distance} km`)
console.log(`Duration: ${result.duration} min.`)
this.mapView.fitToCoordinates(result.coordinates, {
edgePadding: {
right: (width / 20),
bottom: (height / 20),
left: (width / 20),
top: (height / 20),
}
});
}}
onError={(errorMessage) => {
// console.log('GOT AN ERROR');
}}
/>
)}
</MapView>
);
}
}
export default Example;
Can someone help me convert this code from class base component to functional base component.
Thanks.

i'm not react native use (i only use the reactjs) but this is my try it may help (i can't test if it work or not).
import React from 'react'
import { useState ,useRef} from 'react';
import { Dimensions, StyleSheet } from 'react-native';
import MapView from 'react-native-maps';
import MapViewDirections from 'react-native-maps-directions';
const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 37.771707;
const LONGITUDE = -122.4053769;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const GOOGLE_MAPS_APIKEY = '…';
const Example = () => {
const [coordinates, setCoordinates] = useState([{
latitude: 37.3317876,
longitude: -122.0054812,
},
{
latitude: 37.771707,
longitude: -122.4053769,
},
],)
let mapView = useRef(null)
let onMapPress = (e) => {
setCoordinates((coordinates) =>
[
...coordinates,
e.nativeEvent.coordinate,
],
);
}
return (
<MapView
initialRegion={{
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
}}
style={StyleSheet.absoluteFill}
ref={c => mapView.current = c}
onPress={onMapPress}
>
{coordinates.map((coordinate, index) =>
<MapView.Marker key={`coordinate_${index}`} coordinate={coordinate} />
)}
{(coordinates.length >= 2) && (
<MapViewDirections
origin={coordinates[0]}
waypoints={(coordinates.length > 2) ? coordinates.slice(1, -1) : undefined}
destination={coordinates[coordinates.length - 1]}
apikey={GOOGLE_MAPS_APIKEY}
strokeWidth={3}
strokeColor="hotpink"
optimizeWaypoints={true}
onStart={(params) => {
console.log(`Started routing between "${params.origin}" and "${params.destination}"`);
}}
onReady={result => {
console.log(`Distance: ${result.distance} km`)
console.log(`Duration: ${result.duration} min.`)
mapView.current.fitToCoordinates(result.coordinates, {
edgePadding: {
right: (width / 20),
bottom: (height / 20),
left: (width / 20),
top: (height / 20),
}
});
}}
onError={(errorMessage) => {
// console.log('GOT AN ERROR');
}}
/>
)}
</MapView>
)
}
export default Example;

Related

React-map-gl cluster array is empty

I'm using the user-supercluster library to portray clusters on my map. I've upgraded React-map-gl to the newest version. The map shows up but I cannot get my clusters or markers to show up. When I console the cluster array it's completely empty.
When I console log the points, the data is there. I'm not sure what is the issue.
import React, { useRef } from "react";
import Map, {
Marker,
FullscreenControl,
NavigationControl,
} from "react-map-gl";
import HotelsQueryContext from "#Src/routes/Hotels/HotelsQueryContext";
import { useSelector } from "react-redux";
import { hotelsSelectors } from "#Src/redux/rootSelectors";
import useSupercluster from "use-supercluster";
import HotelMarker from "./HotelMarker";
import HotelCardWeb from "../../Web/components/HotelCardWeb";
import HotelCardPhone from "../../Phone/components/HotelCardPhone";
import "mapbox-gl/dist/mapbox-gl.css";
import "./Map.scss";
function HotelMap() {
const mapRef = useRef();
const hotelsQuery = React.useContext(HotelsQueryContext);
const filteredHotels = useSelector(hotelsSelectors.getHotelsFilteredList);
const [viewport, setViewport] = React.useState({
latitude: parseFloat(hotelsQuery.lat),
longitude: parseFloat(hotelsQuery.lon),
zoom: 11.3,
});
const [selectedHotel, setSelectedHotel] = React.useState(null);
//Covert filtered hotels to geojson data objects
const points = filteredHotels.map((hotel) => ({
type: "Feature",
properties: {
cluster: false,
hotel_id: hotel.access_hotel_id,
category: hotel.name,
},
geometry: {
type: "Point",
coordinates: [hotel.location.lon, hotel.location.lat],
},
}));
const bounds = mapRef.current
? mapRef.current.getMap().getBounds().toArray().flat()
: null;
const { clusters, supercluster } = useSupercluster({
points,
bounds,
zoom: viewport.zoom,
options: { radius: 100, maxZoom: 20, minPoints: 9 },
});
//Handles zoom when user clicks on cluster
const clusterZoom = (clusterID, latitude, longitude) => {
mapRef.current?.flyTo({ cener: [longitude, latitude], duration: 2000 });
const expansionZoom = Math.min(
supercluster.getClusterExpansionZoom(clusterID),
20
);
setViewport({
...viewport,
latitude,
longitude,
zoom: expansionZoom,
transitionInterpolator: new FlyToInterpolator({
speed: 2,
}),
transitionDuration: "auto",
});
};
console.log({ clusters });
return (
<div className="searchResults--map-wrapper">
<div className="Map">
<Map
initialViewState={{
...viewport,
}}
mapStyle={process.env.REACT_APP_MAPBOX_STYLE_URL}
mapboxAccessToken={process.env.REACT_APP_MAPBOX_ACCESS_TOKEN}
onMove={(e) => setViewport(e.viewState)}
onClick={() => setSelectedHotel(null)}
style={{
width: "100%",
height: "100%",
}}
ref={mapRef}
>
{clusters.map((cluster, idx) => {
const hotel = filteredHotels.find(
(el) => el.access_hotel_id === cluster.properties.hotel_id
);
const [longitude, latitude] = cluster.geometry.coordinates;
const { cluster: isCluster, point_count } = cluster.properties;
if (isCluster) {
return (
<Marker
key={cluster.id}
latitude={latitude}
longitude={longitude}
>
<div
className="Map-clusterMarker"
onClick={() => clusterZoom(cluster.id, latitude, longitude)}
>
{point_count} hotels
</div>
</Marker>
);
}
return (
hotel && (
<HotelMarker
key={hotel.access_hotel_id}
hotel={hotel}
selectedHotel={selectedHotel}
setSelectedHotel={setSelectedHotel}
/>
)
);
})}
<div className="Map-fullScreenCtrl">
<FullscreenControl />
</div>
<div className="Map-navigationCtrl">
<NavigationControl showCompass={false} />
</div>
</Map>
</div>
</div>
);
}
export default HotelMap;
```React

Real-time Location Functionality is not working. `animateMarkerToCoordinate` function is not Moving to the Specified Coordinates

I'm working on a project and I need to show the real-time location of the user on the map. Below is my code for this functionality but that is not working as I expected. It looks like animateMarkerToCoordinate function is not working properly(not moving the marker to the specified coordinartes) but I don't know surely. Can anyone please help me regarding this? Thank you.
import { StyleSheet, View, Dimensions, Platform } from "react-native";
import React, { useEffect, useState, useRef } from "react";
import MapView, { AnimatedRegion, MarkerAnimated } from "react-native-maps";
import Header from "../components/Header";
import ErrorDialog from "../components/ErrorDialog";
import * as Location from "expo-location";
const { width, height } = Dimensions.get("window");
const ASPECT_RATIO = width / height;
const LATITUDE = 30.3753;
const LONGITUDE = 69.3451;
const LATITUDE_DELTA = 0.005;
const LONGITUDE_DELTA = 0.005;
const LiveLocation = ({ navigation }) => {
const markerRef = useRef(null);
const [error, setError] = useState("");
const [latlng, setLatlng] = useState({ lat: LATITUDE, lng: LONGITUDE });
const [coordinates, setCoordinates] = useState(
new AnimatedRegion({
latitude: LATITUDE,
longitude: LONGITUDE,
latitudeDelta: 0,
longitudeDelta: 0,
})
);
const getMapRegion = () => ({
latitude: latlng.lat,
longitude: latlng.lng,
latitudeDelta: LATITUDE_DELTA,
longitudeDelta: LONGITUDE_DELTA,
});
useEffect(() => {
let unsubscribe;
Location.requestForegroundPermissionsAsync().then((response) => {
if (response.status === "granted") {
Location?.watchPositionAsync(
{
accuracy: Location.Accuracy.BestForNavigation,
timeInterval: 2000,
},
(location_update) => {
const duration = 500;
const { latitude, longitude } = location_update.coords;
const newCoordinates = {
latitude,
longitude,
};
if (Platform.OS === "android") {
if (markerRef) {
markerRef.current.animateMarkerToCoordinate(
newCoordinates,
duration
);
}
}
setLatlng({ lat: latitude, lng: longitude });
}
)
.then((unsub) => {
unsubscribe = unsub;
})
.catch((error) => {
console.log(error.message);
});
} else {
setError("Permission to access location was denied");
}
});
return () => {
unsubscribe.remove();
};
}, []);
return (
<View style={[StyleSheet.absoluteFillObject, styles.container]}>
<MapView
style={[StyleSheet.absoluteFillObject, styles.mapView]}
loadingEnabled
region={getMapRegion()}
>
<MarkerAnimated ref={markerRef} coordinate={coordinates} />
</MapView>
<View style={styles.heading}>
<Header text="Live Location" navigation={() => navigation.goBack()} />
</View>
<View>
<ErrorDialog
visible={!!error}
errorHeader={"Error!"}
errorDescription={error}
clearError={() => {
setError("");
}}
/>
</View>
</View>
);
};
export default LiveLocation;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignContent: "center",
},
mapView: {
flex: 1,
},
heading: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 },
});

How to place my dispatch function with React and React Leaflet for return coords lat + lng

I have an input (on children component) that return coordinates by props (text):
import React, { useEffect, useState, useRef, useMemo } from 'react'
import { useEnderecoValue } from '../../contexts/EnderecoContext'
import 'leaflet/dist/leaflet.css'
import Leaflet from 'leaflet'
import { MapContainer, Marker, useMap, TileLayer, Popup } from 'react-leaflet'
export default function App(text: any) {
const [lat, setLat] = useState(48.856614)
const [lng, setLng] = useState(2.3522219)
const [state, dispatch] = useEnderecoValue()
const icon = new Leaflet.DivIcon({
className: 'custom-div-icon',
html:
"<div style='background-color:#c30b82;' class='marker-pin'></div><i class='material-icons'><img src='img/marker-icon.png'></i>",
iconSize: [30, 42],
iconAnchor: [15, 42],
popupAnchor: [-3, -42]
})
useEffect(() => {
if (text.text) {
setLat(text.text.features[0].geometry.coordinates[1])
setLng(text.text.features[0].geometry.coordinates[0])
}
}, [text])
function SetViewOnClick({ coords }: any) {
const map = useMap()
map.flyTo(coords, map.getZoom())
return null
}
My Marker is draggable and the popup display address and coords if I search address on input, or if the Marker is dragded:
const markerRef = useRef(null)
const eventHandlers = useMemo(
() => ({
dragend() {
const marker = markerRef.current
if (marker != null) {
const { lat, lng } = marker.getLatLng()
setLat(lat)
setLng(lng)
}
}
}),
[]
)
const popup = () => {
if (text.text) {
return text.text.query + ' ' + `lat: ${lat}, long: ${lng}`
}
return (
"Address by default" +
' ' +
`lat: ${lat}, long: ${lng}`
)
}
return (
<MapContainer
center={[lat, lng]}
attributionControl={false}
zoomControl={false}
zoom={18}
style={{
height: '350px',
position: 'relative',
outline: 'none',
maxWidth: '696px',
display: 'block',
margin: '15px auto',
width: '100%'
}}
>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<Marker
position={[lat, lng]}
icon={icon}
draggable={true}
ref={markerRef}
eventHandlers={eventHandlers}
>
<Popup>
<span>{popup()}</span>
</Popup>
<SetViewOnClick coords={[lat, lng]} />
</Marker>
</MapContainer>
)
}
How to place my dispatch function for return coords when I search address and whenthe Marker is dragged ? (just when the value change)
dispatch({
type: 'SET_COORDS',
latitude: lat,
longitude: lng
})
On your Search comp place dispatch inside searchLocation to be able to change lat long.
const searchLocation = async () => {
fetch(
"https://api-adresse.data.gouv.fr/search?" +
new URLSearchParams({
q: state.location,
})
)
.then((data) => data.json())
.then((text) => {
setResp({ text });
dispatch({
type: "SET_COORDS",
latitude: text.features[0].geometry.coordinates[1],
longitude: text.features[0].geometry.coordinates[0],
});
})
.catch(function (error) {
console.log("request failed", error);
});
};
Replace <Map {...resp} /> with <Map text={resp} /> because it causes multiple rerenders and the page becomes unresponsive. Not sure what you were trying to do.
Demo

Custom marker not rerendering after coordinates change

I'm trying to move some markers around the map but after update the array which contains the info about the coordinates of them nothing happens, it's just blinking but not moving.
BusMarker.js (Customer Marker)
import React, { useState, useEffect } from 'react'
import { Marker } from 'react-native-maps'
const BusMarker = props => {
const { longitude, latitude, plate } = props
const [coordinates] = useState({
longitude: Number(longitude),
latitude: Number(latitude)
})
return (
<Marker
key={`bus-${plate}-${new Date().getMilliseconds()}`}
coordinate={coordinates}
image={require('../../img/icon/bus-icon.png')}>
</Marker>
)
}
export default BusMarker
Map.js
import React, { useState, useEffect, Fragment, useCallback, useRef } from 'react'
import { StyleSheet, View, Text } from 'react-native'
import { useSelector, useDispatch } from 'react-redux'
import MapView, { PROVIDER_GOOGLE} from 'react-native-maps'
import BusMarker from './BusMarker'
import { useFocusEffect } from 'react-navigation-hooks'
const Maps = () => {
const [busLocation, setBusLocation] = useState([
{
"plate": "CAR1203",
"latitude": 0,
"longitude": 0,
},
])
const [region] = useState({
latitude: 0,
longitude: 0,
latitudeDelta: 0.0143,
longitudeDelta: 0.0134,
})
const _renderBusesOnMap = busLocation => {
if (busLocation.length > 0) {
return busLocation.map(({ plate, longitude, latitude }) => {
return (
<BusMarker key={plate} plate={plate} longitude={longitude} latitude={latitude} />
)
})
}
}
const updateLocations = () => {
const newPosition = [
{
"plate": "CAR1203",
"latitude": 0,
"longitude": 1,
},
]
const timeout = setInterval(() => {
setBusLocation(newPosition)
}, 1000)
}
useEffect(updateLocations, [])
return (
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
initialRegion={region}
showsCompass={false}
showsTraffic={false}
showsIndoors={false}
showsBuildings={false}
showsPointsOfInterest={false}
loadingEnabled={false}
>
{_renderBusesOnMap(busLocation)}
</MapView>
)
}
const styles = StyleSheet.create({
map: {
flex: 1,
backgroundColor: '#ffff',
height: '100%'
},
})
export default Maps
Why is it not updating its location on the map?
I'm using the following versions on this project:
React Native: 0.60.5
React Native Maps: 0.25.0
Try this instead:
const BusMarker = props => {
const { longitude, latitude, plate } = props;
const coordinates = {
longitude: Number(longitude),
latitude: Number(latitude)
};
return (
<Marker
key={`bus-${plate}-${new Date().getMilliseconds()}`}
coordinate={coordinates}
/>
);
};
const Maps = () => {
const [busLocation, setBusLocation] = useState([
{
plate: "CAR1203",
latitude: 37.78825,
longitude: -122.4324
}
]);
const [region] = useState({
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
});
const _renderBusesOnMap = busLocation => {
if (busLocation.length > 0) {
return busLocation.map(({ plate, longitude, latitude }) => {
return (
<BusMarker
key={plate}
plate={plate}
longitude={longitude}
latitude={latitude}
/>
);
});
}
};
const updateLocations = () => {
const newPosition = [
{
plate: "CAR1203",
latitude: 37.78845,
longitude: -122.4424
}
];
const timeout = setInterval(() => {
setBusLocation(newPosition);
}, 1000);
};
useEffect(updateLocations, []);
return (
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
initialRegion={region}
showsCompass={false}
showsTraffic={false}
showsIndoors={false}
showsBuildings={false}
showsPointsOfInterest={false}
loadingEnabled={false}
>
{_renderBusesOnMap(busLocation)}
</MapView>
);
};
The main problem I think is the way you set coordinates inside BusMarker. There is no need to use useState there. You can just set coordinates as an object with the latitude and longitude values you receive from props.
I've adjusted the coordinates to make it easier to test.
Update
I will try to add to this answer, by explaining my understanding of the behavior. I will explain by giving a more general example. So let's say you have code like this:
const Component = props => {
const [data, setData] = useState(props);
// ...
Now props will be set as the initial value of data.
But useState won't update on props change this way. So even though the props have changed, data still holds the value it was initially passed. To tell data to update on props change you could use useEffect inside Component:
useEffect(() => {
setData(props);
}, [props]);
But like I said in my original answer, there is no reason to use useState in this situation. We don't need to keep Component's state in sync with the parent props as we can simply use props directly. So don't use the useEffect approach I've given as it's unnecessary, it's just to give an example.

How to get the leaves from a cluster using react mapbox gl?

I have tried on example from Youtube tutorial "Mapbox Marker clustering". I want to add getLeaves method to grab all of the items inside the cluster in order to visualize a popup with each details for every individual marker.
console.log(cluster.id) returns id of each cluster id, but
const items = supercluster.getLeaves(cluster.id) gets an error that "Error: No cluster with the specified id."
Is it possible to use getLeaves method with react mapbox gl?
For this react code and link to youtube video.
--
import React, { useState, useRef } from 'react';
import useSwr from "swr";
import ReactMapGL, { Marker, FlyToInterpolator, NavigationControl } from 'react-map-gl';
import useSupercluster from 'use-supercluster';
import Supercluster from 'supercluster';
import './App.css'
const fetcher = (...args) => fetch(...args).then(response => response.json());
export default function App(){
//navigation control
const navStyle = {
position: "absolute",
top: 0,
right: 10,
padding: "10px"
};
//set up map
const [viewport, setViewport ] = useState({
latitude: 52.6376,
longitude: -1.135171,
width: "100vw",
height: "100vh",
zoom: 12
});
const mapRef = useRef();
// load and prepare data
const url =
"https://data.police.uk/api/crimes-street/all-crime?lat=52.629729&lng=-1.131592&date=2019-10";
const {data, error} = useSwr(url, fetcher);
const crimes = data && !error ? data.slice(0, 200) : [];
const points = crimes.map(crime => ({
type: "Feature",
properties: {
cluster: false,
crimeId: crime.id,
category: crime.category
},
geometry: {
type: "Point",
coordinates: [
parseFloat(crime.location.longitude),
parseFloat(crime.location.latitude)
]
}
}));
// get map bounds
const bounds = mapRef.current
? mapRef.current
.getMap()
.getBounds()
.toArray()
.flat()
: null;
// get clusters
const {clusters, supercluster } = useSupercluster({
points,
zoom: viewport.zoom,
bounds,
options: { radius: 75, maxZoom: 20 }
});
// return map
return(
<div>
<ReactMapGL
{...viewport}
maxZoom={20}
mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN123}
mapStyle="mapbox://styles/shunsukeito/ckaanisi33g1k1ipowgmg8tdd"
onViewportChange={newViewport => {
setViewport({...newViewport});
}}
ref={mapRef}
>
<div className="nav" style={navStyle}>
<NavigationControl
/>
</div>
{clusters.map(cluster => {
const [longitude, latitude] = cluster.geometry.coordinates;
const {
cluster: isCluster,
point_count: pointCount
} = cluster.properties;
//where I get the error!!!
console.log(cluster.id)
const items = supercluster.getLeaves(cluster.id)
if (isCluster){
return(
<Marker key={cluster.id} latitude={latitude} longitude={longitude}>
<div className="cluster-marker"
style={{
width: '${10 + PointCount / points.lenght) * 50}px',
height: '${10 + PointCount / points.lenght) * 50}px'
}}
onClick={() => {
const expansionZoom = Math.min(
supercluster.getClusterExpansionZoom(cluster.id),
20
);
setViewport({
...viewport,
latitude,
longitude,
zoom: expansionZoom,
transitionInterpolator: new FlyToInterpolator({
spped: 2
}),
transitionDuration: "auto"
});
}}
>
{pointCount}
</div>
</Marker>
);
}
return (
<Marker
key={cluster.properties.crimeId}
latitude={latitude}
longitude={longitude}
>
<button className="crime-marker"
>
<img src="/custody.svg" alt="crime doesn't pay" />
</button>
</Marker>
);
})}
</ReactMapGL>
</div>
);
}
--
https://www.youtube.com/watch?v=3HYvbP2pQRA&t=12s
Any suggestions are welcomed.
Thank you,

Resources