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 component:
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 component:
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 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 component.
Markers component:
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 component only fetches data when showCallOut is true. In Callout component
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: