I'm new to React so I hope I'm approaching this problem correctly. First I have a screen called SearchLocationsScreen. Inside that screen I have a component called Map and inside of Map I have custom marker components called LocationMarker. At the same hierarchical level as the Map component, I have a custom ModalBox called CheckinModal. Here is a crude diagram to help:
In SearchLocationsScreen I am getting the location info from an API call. Then I pass those locations down to my Map component. Inside my Map component I pass the information for the markers down to the custom LocationMarker class and populate the map.
The goal is to press on a marker and have the CheckinModal pop up from the bottom and populate it with info from the particular marker that was pressed. To do this, I use the useRef hook and forwardRef hook to pass a reference to the modal down to the LocationMarker class. Here I call ref.current.open()
and the modal opens as expected.
The problem is that I can't figure out a way to pass the location info from the marker, back up the hierarchy to the screen and down to the modal to populate the modal with relevant info. Does anyone know how to achieve this? I'm posting the code below to my screen, map component and marker component (styles not included). Thanks in advance for your help.
SearchLocationsScreen.js
const SearchLocationsScreen = ({isFocused, navigation}) => {
const {updateLocation} = useContext(CurrentLocationContext);
// hooks
const callback = useCallback((location) => {
updateLocation(location)
}, []);
const [err] = useCurrentLocation(isFocused, callback);
const [businessLocations] = useGetLocations();
const modalRef = useRef(null);
let locations = [];
if (businessLocations) {
for (let x = 0; x < businessLocations.length; x++) {
locations.push({
...businessLocations[x],
latitude: businessLocations[x].lat,
longitude: businessLocations[x].lng,
key: x,
})
}
}
return (
<View style={{flex: 1}}>
<Map markers={locations} ref={modalRef}/>
<SearchBar style={styles.searchBarStyle}/>
{err ? <View style={styles.errorContainer}><Text
style={styles.errorMessage}>{err.message}</Text></View> : null}
<CheckinModal
ref={modalRef}
/>
</View>
);
};
Map.js
const Map = ({markers}, ref) => {
const {state: {currentLocation}} = useContext(Context);
// todo figure out these error situations
if (!currentLocation) {
return (
<View style={{flex: 1}}>
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: 27.848680,
longitude: -82.646560,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
/>
<ActivityIndicator size='large' style={styles.indicator} />
</View>
)
}
return (
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
...currentLocation.coords,
latitudeDelta: regions.latDelta,
longitudeDelta: regions.longDelta
}}
showsUserLocation
>
{ markers ? markers.map((marker, index) => {
return <LocationMarker
ref={ref} // passing the ref down to the markers
key={index}
coordinate={marker}
title={marker.company}
waitTime={ marker.wait ? `${marker.wait} minutes` : 'Open'}
/>;
}) : null}
</MapView>
)
};
const forwardMap = React.forwardRef(Map);
export default forwardMap;
LocationMarker.js
const LocationMarker = ({company, coordinate, title, waitTime, onShowModal}, ref) => {
return (
<View>
<Marker
coordinate={coordinate}
title={title}
onPress={() => {
console.log(ref);
ref.current.open();
}}
>
<Image
source={require('../../assets/marker2.png')}
style={styles.locationMarker}/>
<View style={styles.waitContainer}><Text style={styles.waitText}>{waitTime}</Text></View>
</Marker>
</View>
)
};
const forwardMarker = React.forwardRef(LocationMarker);
export default forwardMarker;
If I understood correctly, instead of using forwardRef
to pass the ref from the parent using the ref
prop, I suggest passing it as a simple prop. When it reaches the nested component (LocationMarker in your case) you can assign it. This is a simplified version:
const SearchLocationsScreen = props => {
const marker_ref = useRef(null);
const modal_ref = useRef(null);
return (
<View>
<Map marker_ref={marker_ref} modal_ref={modal_ref} />
<CheckinModal marker_ref={marker_ref} modal_ref={modal_ref} />
</View>
);
};
const Map = props => {
const { marker_ref, modal_ref } = props;
return <LocationMarker marker_ref={marker_ref} modal_ref={modal_ref} />;
};
const LocationMarker = props => {
const { marker_ref, modal_ref } = props;
return <div ref={marker_ref} />;
};
const CheckinModal = props => {
const { marker_ref, modal_ref } = props;
return <div ref={modal_ref} />;
};
When the ref reaches the final element we assign it using ref=
. Remember that this final element needs to be a JSX element, like a div
, and not a component.
To avoid passing these props from the grandparent to the children through each component in between, you could use a Context in SearchLocationsScreen
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With