I am using react-native-maps
to display markers for train stations in my area. Each marker has a Callout with real time data of approaching trains.
The issue is; every callout is being rendered in the background for every marker I have on the map. Also each callout is being re-rendered as I have new data from real time API. This is causing hundreds of views being rendered even though I only need the callout of the marker that is pressed.app screenshot
Is there a way to make sure no callout is being rendered until user presses on a specific marker? After the press; I also want to make sure only that specific marker's callout is being rendered and displayed.
My code:
MapScreen:
const MapScreen = props => {
// get user location from Redux store
// this is used to center the map
const { latitude, longitude } = useSelector(state => state.location.coords)
// The MapView and Markers are static
// We only need to update Marker callouts after fetching data
return(
<SafeAreaView style={{flex: 1}}>
<MapView
style={{flex: 1}}
initialRegion={{
latitude: parseFloat(latitude) || 37.792874,
longitude: parseFloat(longitude) || -122.39703,
latitudeDelta: 0.06,
longitudeDelta: 0.06
}}
provider={"google"}
>
<Markers />
</MapView>
</SafeAreaView>
)
}
export default MapScreen
Markers ponent:
const Markers = props => {
const stationData = useSelector(state => state.stationData)
return stationData.map((station, index) => {
return (
<MapView.Marker
key={index}
coordinate={{
// receives station latitude and longitude from stationDetails.js
latitude: parseFloat(stationDetails[station.abbr].gtfs_latitude),
longitude: parseFloat(stationDetails[station.abbr].gtfs_longitude)
}}
image={stationLogo}
zIndex={100}
tracksInfoWindowChanges={true}
>
<MapView.Callout
key={index}
tooltip={true}
style={{ backgroundColor: "#ffffff" }}
>
<View style={styles.calloutHeader}>
<Text style={{ fontWeight: "bold" }}>{station.name}</Text>
</View>
<View style={styles.calloutContent}>
<StationCallout key={index} station={stationData[index]} />
</View>
</MapView.Callout>
</MapView.Marker>
);
});
};
StationCallout ponent:
const StationCallout = (props) => {
return(
props.station.etd.map((route, index) => {
const approachingTrains = function() {
trainText = `${route.destination} in`;
route.estimate.map((train, index) => {
if (index === 0) {
if (train.minutes === "Leaving") {
trainText += ` 0`;
} else {
trainText += ` ${train.minutes}`;
}
} else {
if (train.minutes === "Leaving") {
trainText += `, 0`;
} else {
trainText += `, ${train.minutes}`;
}
}
});
trainText += " mins";
return <Text>{trainText}</Text>;
};
return <View key={index}>
{approachingTrains()}
</View>;
})
)
};
export default StationCallout
I am using react-native-maps
to display markers for train stations in my area. Each marker has a Callout with real time data of approaching trains.
The issue is; every callout is being rendered in the background for every marker I have on the map. Also each callout is being re-rendered as I have new data from real time API. This is causing hundreds of views being rendered even though I only need the callout of the marker that is pressed.app screenshot
Is there a way to make sure no callout is being rendered until user presses on a specific marker? After the press; I also want to make sure only that specific marker's callout is being rendered and displayed.
My code:
MapScreen:
const MapScreen = props => {
// get user location from Redux store
// this is used to center the map
const { latitude, longitude } = useSelector(state => state.location.coords)
// The MapView and Markers are static
// We only need to update Marker callouts after fetching data
return(
<SafeAreaView style={{flex: 1}}>
<MapView
style={{flex: 1}}
initialRegion={{
latitude: parseFloat(latitude) || 37.792874,
longitude: parseFloat(longitude) || -122.39703,
latitudeDelta: 0.06,
longitudeDelta: 0.06
}}
provider={"google"}
>
<Markers />
</MapView>
</SafeAreaView>
)
}
export default MapScreen
Markers ponent:
const Markers = props => {
const stationData = useSelector(state => state.stationData)
return stationData.map((station, index) => {
return (
<MapView.Marker
key={index}
coordinate={{
// receives station latitude and longitude from stationDetails.js
latitude: parseFloat(stationDetails[station.abbr].gtfs_latitude),
longitude: parseFloat(stationDetails[station.abbr].gtfs_longitude)
}}
image={stationLogo}
zIndex={100}
tracksInfoWindowChanges={true}
>
<MapView.Callout
key={index}
tooltip={true}
style={{ backgroundColor: "#ffffff" }}
>
<View style={styles.calloutHeader}>
<Text style={{ fontWeight: "bold" }}>{station.name}</Text>
</View>
<View style={styles.calloutContent}>
<StationCallout key={index} station={stationData[index]} />
</View>
</MapView.Callout>
</MapView.Marker>
);
});
};
StationCallout ponent:
const StationCallout = (props) => {
return(
props.station.etd.map((route, index) => {
const approachingTrains = function() {
trainText = `${route.destination} in`;
route.estimate.map((train, index) => {
if (index === 0) {
if (train.minutes === "Leaving") {
trainText += ` 0`;
} else {
trainText += ` ${train.minutes}`;
}
} else {
if (train.minutes === "Leaving") {
trainText += `, 0`;
} else {
trainText += `, ${train.minutes}`;
}
}
});
trainText += " mins";
return <Text>{trainText}</Text>;
};
return <View key={index}>
{approachingTrains()}
</View>;
})
)
};
export default StationCallout
Share
Improve this question
asked Jan 22, 2020 at 17:30
Onur EkerOnur Eker
9401 gold badge8 silver badges11 bronze badges
3 Answers
Reset to default 0On ComponentDidMount, you should fetch data for all trains so that all markers can be set on their positions. you can do this using once('value') event of firebase this event only fetches data from a refernce once when it is called so you will call it on ponent did mount.
READ MORE ABOUT ONCE('VALUE')
Now as you have all the pointers on their positions, user can click any one of the pointer to Track It's movement right?
So each pointer must have something unique, like Train ID or something i dont know your database structure so i am assuming that you have train ID, Now in the onPress function of marker you should pass this TrainID.
example:
onPress={()=> this.TrackSpecificTrain(trainID) }
Now in TrackSpecificTrain
function you should call your database refence with the train ID and firebase on('value') event, Now you will keep getting real time data of the selected train and you can update your local state stationData
with the new data ing from firebase.
Example
TrackSpecificTrain=(trainID)=>{
const ref = database().ref(`YourTrainsRef/${trainID}/`)
ref.on('value',( snapshot)=>{
//Update your local state with new data in snapshot
})
}
RemoveTracker=(trainID)=>{
const ref = database().ref(`YourTrainsRef/${trainID}/`)
ref.off("value")
}
Now here we are also using RemoveTracker
because you may need to stop tracking previous train if user clicks on another marker so it will start tracking the trainID on the new marker and stop tracking previous one!.
I actually found the answer myself. I have created a reference to each marker, then passed an onPress property to <MapView.Marker> and showCallout property to its Callout ponent.
Markers ponent:
export default function Markers() {
const {
stations: { station }
} = require("../../bartData/stations");
const [clickedMarkerRef, setClickedMarkerRef] = useState(null)
return station.map((trainStation, index) => {
return (
<MapView.Marker
key={trainStation.abbr}
coordinate={{
latitude: parseFloat(trainStation.gtfs_latitude),
longitude: parseFloat(trainStation.gtfs_longitude)
}}
image={Platform.OS === "ios" ? station_ios : station_android}
zIndex={100}
tracksInfoWindowChanges={true}
onPress={() => setClickedMarkerRef(index)}
>
<CalloutContainer
key={trainStation.abbr}
stationName={trainStation.name}
stationAbbr={trainStation.abbr}
showCallOut={clickedMarkerRef === index}
/>
</MapView.Marker>
);
});
}
And Callout ponent only fetches data when showCallOut is true. In Callout ponent
useEffect(() => {
if (props.showCallOut === true) {
fetchTrainDepartures();
const intervalId = setInterval(fetchTrainDepartures, 10000);
return () => clearInterval(intervalId);
}
}, []);
So, unless you click on a marker, the local state stays at null and callouts doesn't fetch any data.
When you click on marker at index 0:
- clickedMarkerRef is now 0.
- showCallout is true => {clickMarkerRef === index}
- on Callout.js file under useEffect hook => props.showCallout is true.
- Data is fetched only for this callout.
Inside MapView use Marker, Callout like this. This callout view disappears when user clicks on other marker(then new markers view appears) or on map itself.
<Marker key={i} coordinate={{latitude:latitude, longitude:longitude}}>
<Callout tooltip={true} onPress={()=>this.processCalloutClick(item)}>
<View style={{backgroundColor:'white',width:80, height:40, alignItems:'center'}}>
<Text>{item.locationName}{"\n"}{"...More info"}</Text>
</View>
</Callout>
</Marker>