Как общаться от внука до бабушки и дедушки, а затем вернуться к ребенку дедушки в React Native - PullRequest
5 голосов
/ 07 октября 2019

Я новичок в React, поэтому надеюсь, что я правильно подхожу к этой проблеме. Сначала у меня есть экран с именем SearchLocationsScreen . Внутри этого экрана у меня есть компонент с именем Карта , а внутри Карта У меня есть пользовательские компоненты маркера, называемые LocationMarker . На том же уровне иерархии, что и у компонента Map , у меня есть пользовательский ModalBox с именем CheckinModal . Вот приблизительная диаграмма, чтобы помочь:

enter image description here

В SearchLocationsScreen Я получаю информацию о местоположении из вызова API. Затем я передаю эти места моему компоненту Карта . Внутри моего Map компонента я передаю информацию для маркеров в пользовательский класс LocationMarker и заполняю карту.

Цель состоит в том, чтобы нажать на маркер и заставить всплывающий снизу CheckinModal заполнить его информацией о конкретном нажатом маркере. Для этого я использую хук useRef и хук forwardRef , чтобы передать ссылку на модальный класс LocationMarker . Здесь я звоню ref.current.open() и модал открывается как положено.

Проблема в том, что я не могу найти способ передачи информации о местоположении от маркера, обратно вверх по иерархии на экран и вниз к модалу, чтобы заполнить модал соответствующей информацией. Кто-нибудь знает, как этого добиться? Я публикую приведенный ниже код на моем экране, компоненте карты и компоненте маркера (стили не включены). Заранее благодарим за помощь.

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;

Ответы [ 3 ]

1 голос
/ 07 октября 2019

Если я правильно понял, вместо использования forwardRef для передачи ссылки от родителя с помощью ref реквизита, я предлагаю передать его как простую реквизит. Когда он достигает вложенного компонента ( LocationMarker в вашем случае), вы можете назначить его. Это упрощенная версия:

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}  />;
};

Когда ссылка достигает конечного элемента, мы назначаем его с помощью ref=. Помните, что этот последний элемент должен быть элементом JSX, таким как div, а не компонентом.

Чтобы не передавать эти реквизиты от прародителя к потомкам через каждый промежуточный компонент, вы можете использовать Контекст в SearchLocationsScreen.

0 голосов
/ 08 октября 2019

Я понял это с помощью комментария Альваро к моему основному посту.

Вот что я сделал. Сначала я переместил код, который генерирует LocationMarker s, в SearchLocationsScreen . В любом случае я уже обращался к местоположениям, необходимым для этих маркеров на этом экране (первоначально я передавал эти местоположения компоненту Map и создавал их там). В SearchLocationsScreen я перебираю все местоположения, чтобы сгенерировать LocationMarker s, добавляя обратный вызов, который использует хук useReducer для хранения состояния мода. Поскольку они оба находятся на одном уровне, я могу заполнить поля для модальной модели правильными данными из состояния редуктора. Этот обратный вызов передается LocationMarker s. Затем в LocationMarker onPress я вызываю этот метод. Все работает как положено. Вот обновленный код:

SearchLocationsScreen

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,
            })
        }
    }

    const modalReducer = (state, action) => {
        console.log("payload: ", action.payload);
        switch (action.type) {
            case 'show_modal':
                return {...state,
                    companyName: action.payload.companyName,
                    companyAddress: action.payload.companyAddress,
                    waitTime: action.payload.waitTime
                };
            default:
                return state;
        }
    };

    const [modalState, dispatch] = useReducer(modalReducer, {
        companyName: "Company Name",
        companyAddress: "123 Elm St",
        waitTime: 0
    });

    const createMarkers = () => {
        let result = [];
        if (locations) {
            for (let i = 0; i < locations.length; i++) {
                result.push(
                    <LocationMarker
                        key={i}
                        id={i}
                        coordinate={locations[i]}
                        title={locations[i].company}
                        waitTime={locations[i].wait ? `${locations[i].wait} minutes` : 'Closed'}
                        onShowModal={() => {
                            dispatch({
                                type: 'show_modal', payload: {
                                    companyName: locations[i].company,
                                    companyAddress: locations[i].address,
                                    waitTime: locations[i].wait,
                                }
                            });
                            modalRef.current.open();
                        }}
                    />
                )
            }
        }
        return result;
    };

    return (
        <View style={{flex: 1}}>

            <Map markers={createMarkers()}/>
            {/*<Map ref={modalRef} markers={...createMarkers()} />*/}

            <SearchBar style={styles.searchBarStyle}/>

            {err ? <View style={styles.errorContainer}><Text
                style={styles.errorMessage}>{err.message}</Text></View> : null}

            <CheckinModal
                ref={modalRef}
                businessName={modalState.companyName}
                address={modalState.companyAddress}
                waitTime={modalState.waitTime}
            />

        </View>
    );
};

Карта

const Map = ({markers}, ref) => {

    const {state: {currentLocation}} = useContext(Context);

    return (

        <MapView
            style={styles.map}
            provider={PROVIDER_GOOGLE}
            initialRegion={{
                ...currentLocation.coords,
                latitudeDelta: regions.latDelta,
                longitudeDelta: regions.longDelta
            }}
            showsUserLocation
            >

            {markers ? markers.map((marker, index) => {
                return marker;
            }): null}

        </MapView>
    )
};

export default Map;

CheckinModal

const CheckinModal = ({businessName, address, waitTime}, ref) => {

    return (
            <ModalBox
                style={styles.modal}
                position={'bottom'}
                backdrop={true}
                ref={ref}
            >
                <Text>Store Name: {businessName}</Text>
                <Text>Store Address: {address}</Text>
                <Text>Wait time: {waitTime} minutes</Text>
            </ModalBox>
    )
};

const forwardModal = React.forwardRef(CheckinModal);
export default forwardModal;

LocationMarker

const LocationMarker = (props) => {

    return (
        <View>
            <Marker
                coordinate={props.coordinate}
                title={props.title}
                id={props.id}
                onPress={() => {
                    props.onShowModal();
                }}
            >
                <Image
                    source={require('../../assets/marker2.png')}
                    style={styles.locationMarker}/>
                <View style={styles.waitContainer}>
                    <Text style={styles.waitText}>{props.waitTime}</Text>
                </View>
            </Marker>

        </View>
    )
};

export default LocationMarker;

С этим новым кодом мне больше не нужно пересылать ссылки, только на CheckinModal .

Если у кого-то возникнут вопросы, напишите их под этим ответом, и я постараюсь ответить на них, как только смогу. Спасибо всем за помощь.

0 голосов
/ 07 октября 2019

Думали ли вы об использовании крючка? Хуки позволяют вам функционально составлять компоненты, не пропуская все через реквизиты. Если вам нужно использовать это значение в нескольких местах, это может облегчить вашу жизнь? В вашей ситуации вы устанавливаете ловушку в одном месте, а затем обновляете другой компонент, когда он изменяется, не беспокоясь о передаче чего-либо вокруг

https://reactjs.org/docs/hooks-intro.html

...