Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to communicate from a grandchild up to its grandparent then back down to the grandparent's child in React Native

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:

enter image description here

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;
like image 711
Jo Momma Avatar asked Nov 07 '22 13:11

Jo Momma


1 Answers

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.

like image 66
Alvaro Avatar answered Nov 15 '22 13:11

Alvaro