5

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

3 Answers 3

0

On 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 component 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 coming 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!.

Sign up to request clarification or add additional context in comments.

Comments

0

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:

  • 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.

1 Comment

Hi - can you share your code as full example, trying to get it to work for me
0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.