Как получить данные, когда прокрутить до дна в реагировать родной с firestore?

1

Я использую native-native с redux, redux-thunk и firebase/firestore.

Интересно, как сделать разбивку на страницы с firestore. Я знаю, что это написано в документе, но я не мог понять это.

Теперь я пытаюсь сделать это, когда прокручиваем вниз, и он достигает дна, а затем извлекает следующие данные.

Пример кода ниже.

var first = db.collection("cities")
        .orderBy("population")
        .limit(25);

    return first.get().then(function (documentSnapshots) {
  // Get the last visible document
  var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
  console.log("last", lastVisible);

  // Construct a new query starting at this document,
  // get the next 25 cities.
  var next = db.collection("cities")
          .orderBy("population")
          .startAfter(lastVisible)
          .limit(25);
});

Я использую response-redux, поэтому мне нужно изменить этот код. Поэтому, я думаю, мне нужно сохранить lastVisible как состояние. Но тогда как я могу назвать это событие?

Теги:
firebase
react-native
react-redux
google-cloud-firestore

2 ответа

1

Если вы используете FlatList есть свойство onScrollEndDrag которое будет срабатывать, когда вы попадете в нижнюю часть компонента. Пример:

  <FlatList
       onScrollEndDrag={this.updatePageNumberAndMakeNewRequest}
      />
0

У меня есть попытка решения, которое работает для меня с реактивной базой, редуксом, реактивом и редуксом, реактивной навигацией.

  • Вы можете создать имя каталога "app" внутри вашего проекта и имя каталога "core" внутри каталога "app". Внутри ядра вы создаете имя каталога "actions" и имя каталога "redurs".

  • Внутри каталога редукторов вы создаете вызов файла редуктора, например "firestorePaginatorReducer.js", со следующим содержанием:

import { 
    FIRESTORE_PAGINATOR_LIST, 
    FIRESTORE_PAGINATOR_INIT, 
    FIRESTORE_PAGINATOR_ERROR,
    FIRESTORE_PAGINATOR_RESET 
} from '../actions/types';
import { isEmpty, isArray } from 'lodash';
import { PAGINATION_ITEM_PER_PAGE } from '../../utils/firebase';

const initialState = {};

const init = { 
    data: [],
    page: 1,
    willPaginate: false,
    pageKey: null,
    unsubscribes: [],
    isLoaded: false,
    isError: false,
    isEmpty: true 
};

export default function (state = initialState, action) {
    const value = action.value;
    const key = value ? value.key : null;

    let newState;

    switch (action.type) {
        case FIRESTORE_PAGINATOR_INIT:   
            if(state.hasOwnProperty(key)) {
                newState = {
                    ...state,
                    [key]: {
                        ...state[key],
                        ...init
                    }
                };
            } else {       
                newState = {
                    ...state,
                    [key]: {...init}
                };
            }
            break;

        case FIRESTORE_PAGINATOR_LIST:
            const { data, unsubscribe, isPagination, paginationField } = value;
            const dataLength = state.hasOwnProperty(key) ? state[key].data.length + data.length :
                                data.length;
            const pageNumber = (!isPagination || dataLength === 0) ? 1 :
                                Math.ceil(dataLength / PAGINATION_ITEM_PER_PAGE);
            const willPaginate = dataLength >= pageNumber * PAGINATION_ITEM_PER_PAGE

            newState = {
                ...state,
                [key]: {
                    ...state[key],
                    page: pageNumber,
                    willPaginate: willPaginate,
                    data: !isPagination ? [].concat(data) : state[key].data.concat(data),                
                    pageKey: isArray(data) && data.length > 0 ? data[data.length - 1][paginationField] : null,
                    unsubscribes: state.hasOwnProperty(key) ?
                        [ ...state[key].unsubscribes, unsubscribe ] : [].push(unsubscribe), 
                    isLoaded: true,
                    isError: false,
                    isEmpty: state.hasOwnProperty(key) ? 
                        isEmpty(state[key].data) && isEmpty(data) : isEmpty(data)
                }
            };
            break;

        case FIRESTORE_PAGINATOR_ERROR:
            newState = {
                ...state,
                [key]: {
                    ...state[key],
                    isLoaded: true,
                    isError: true,
                    isEmpty: state.hasOwnProperty(key) ? 
                        isEmpty(state[key].data) : true
                }
            };  
            break;

        case FIRESTORE_PAGINATOR_RESET:
            newState = {
                ...state,
                [key]: {
                    ...state[key],
                    ...init
                }
            };  
            break;

        default:
            newState = state;
            break;
    }

    return newState || state;

}
  • Внутри каталога действий, вы создаете имя файла "actions.js" с этим содержанием ниже
import { 
    FIRESTORE_PAGINATOR_LIST, 
    FIRESTORE_PAGINATOR_INIT, 
    FIRESTORE_PAGINATOR_ERROR, 
    FIRESTORE_PAGINATOR_RESET
} from './types';
import { paginate } from '../../utils/firebase';
import { isArray } from 'lodash';

export const setPaginationListener = (
    query, 
    paginationField, 
    pageKey = null, 
    isPagination = false,
    sort = "DESC"
) => (dispatch, getState)=> {

    if(!isPagination){
        unsetListeners(query, dispatch, getState);

        dispatch({
            type: FIRESTORE_PAGINATOR_INIT,
            value: {
                key: query.storeAs
            }
        });
    } 

    return new Promise(resolve => setTimeout(resolve, 1000)).then(() => {
        const unsubscribe = paginate(
            (querySnapShot) => {
                let data = [];    
                querySnapShot.docs.forEach((snap) => {
                    const doc = snap.data();             
                    data.push({ ...doc, id: snap.id });
                });

                dispatch({
                    type: FIRESTORE_PAGINATOR_LIST,
                    value: {
                        key: query.storeAs,
                        data: data,
                        unsubscribe: unsubscribe,
                        isPagination: isPagination,
                        paginationField: paginationField
                    }
                });
            },
            (error) => { 
                dispatch({ 
                    type: FIRESTORE_PAGINATOR_ERROR,
                    value: {
                        key: query.storeAs
                    }
                });
            },
            createPathFromQuery(query),
            isArray(query.orderBy) ? query.orderBy : paginationField,
            query.where ? query.where : null,
            pageKey,
            query.endAt,
            sort
        );
    }); 
}    

export const unsetPaginationListener = (query) => (dispatch, getState) => {

    unsetListeners(query, dispatch, getState);

    return;
}

export function getStateRequest(data) {
    if(typeof data === "object" && 
        data.hasOwnProperty("isLoaded") && 
        data.hasOwnProperty("isError") &&
        data.hasOwnProperty("isEmpty")
    ) {
        return {
            isLoaded: data.isLoaded, 
            isError: data.isError, 
            isEmpty: data.isEmpty
        };
    } 

    return {
        isLoaded: false, 
        isError: false, 
        isEmpty: true
    };
}

function unsetListeners(query, dispatch, getState) {
    const key = query.storeAs;
    const state = getState().firestorePaginator;
    const unsubscribes = state.hasOwnProperty(key) ? state[key].unsubscribes : null;

    if(isArray(unsubscribes) && unsubscribes.length > 0) {
        unsubscribes.forEach((unsubscribe) => {
            unsubscribe();
        });

        dispatch({
            type: FIRESTORE_PAGINATOR_RESET,
            value: {
                key: key
            }
        });
    }
}

function createPathFromQuery(query) {
    const collection = query.collection;
    const doc = query.doc;
    const subcollections = query.subcollections;

    if(collection) {
        if(doc) {
            if(subcollections && isArray(subcollections)) {
                return collection + "/" + doc + "/" + subcollections[0].collection;
            } else {
                return collection + "/" + doc;
            }
        } else { 
            return collection;
        }
    }

    return null;
}
  • Внутри каталога действий снова вы создаете файл с именем "types.js" с содержанием ниже
// Manage Firestore pagination queries
export const FIRESTORE_PAGINATOR_LIST = "FIRESTORE_PAGINATOR_LIST";
export const FIRESTORE_PAGINATOR_INIT = "FIRESTORE_PAGINATOR_INIT";
export const FIRESTORE_PAGINATOR_ERROR = "FIRESTORE_PAGINATOR_ERROR";
export const FIRESTORE_PAGINATOR_RESET = "FIRESTORE_PAGINATOR_RESET";
  • Вы создаете имя каталога "utils" внутри каталога приложения, затем внутри вы создаете файл с именем "firebase.js" с таким содержанием:
import firebase from 'react-native-firebase';
import { isArray, isString, isEmpty } from 'lodash';

export function getCurrentUserId() {
    return firebase.auth().currentUser.uid;
}

export function getCollection(collection) {
    return firebase.firestore().collection(collection);
}

export function getDoc(collection, id) {
    return getCollection(collection).doc(id);
}

// Create query according to react redux firebase
export function createQuery(
    collection,
    doc = null, 
    subcollections = null,
    orderBy = null, 
    where = null, 
    startAt = null,
    endAt = null,
    limit = null,
    storeAs = null
) {

    const query = {
        collection: collection
    };

    if(doc !== null) {
        query.doc = doc;
    }

    if(subcollections !== null) {
        query.subcollections = subcollections;
    }

    if(orderBy !== null) {
        query.orderBy = orderBy;
    }

    if(where !== null) {
        query.where = where;
    }

    if(startAt !== null) {
        query.startAfter = startAt;
    }

    if(endAt !== null) {
        query.endAt = endAt;
    }

    if(limit !== null) {
        query.limit = limit;
    }

    if(storeAs !== null) {
        query.storeAs = storeAs;
    }

    return query;

}

export const PAGINATION_ITEM_PER_PAGE = 30;

// Firestore paginator
export function paginate(
    callBackSuccess, 
    callBackError,
    collection, 
    orderByPath, 
    whereCond = null, 
    startAt = null, 
    endAt = null,
    sort = "DESC", 
    limit = PAGINATION_ITEM_PER_PAGE
){

    const collectionRef = getCollection(collection); 
    let query = collectionRef;

    if(isArray(whereCond)){
        for(let i = 0; i < whereCond.length; i++) {
            query = query.where(whereCond[i][0], whereCond[i][1], whereCond[i][2]);
        } 
    } 

    if(isString(orderByPath)) {
        query = query.orderBy(orderByPath, sort);
    } else if(isArray(orderByPath)) {
        for(let i = 0; i < orderByPath.length; i++) {
            query = query.orderBy(orderByPath[i][0], orderByPath[i][1]);
        }
    }

    if(startAt !== null && endAt !== null){

        return query.startAfter(startAt).endAt(endAt)
                    .limit(limit).onSnapshot(callBackSuccess, callBackError);

    } else if(startAt === null && endAt !== null) {

        return query.endAt(endAt).limit(limit).onSnapshot(callBackSuccess, callBackError);

    } else if(startAt !== null && endAt === null) {

        return query.startAfter(startAt).limit(limit).onSnapshot(callBackSuccess, callBackError);

    } else {

        return query.limit(limit).onSnapshot(callBackSuccess, callBackError);

    }

}

Чтобы использовать его в компоненте, теперь мы предполагаем, что вы хотите запросить список каналов с разбивкой на страницы по категориям типов и отфильтровать его с разбивкой по именам, вы создаете файл с именем "ListFeed.js" (это предполагает, что вы уже переходите к этот экран из категории экрана) с содержимым что-то вроде:

import React, { Component } from 'react';
import { StyleSheet, View, FlatList } from 'react-native';
import { Input, Icon, Image, Text } from 'react-native-elements';
import { connect } from 'react-redux';
import { setPaginationListener, unsetPaginationListener, getStateRequest } from '../../../core/actions/actions';
import { createQuery, PAGINATION_ITEM_PER_PAGE } from '../../../utils/firebase';
import Spinner from '../../../core/layout/Spinner';
import { isEmpty } from 'lodash';

class ListFeed extends Component {

    constructor(props) {
        super(props);

        this.state = {
            search: ''
        };

        this.query = null;
    }

    componentDidMount() {
        this._loadData();
    }

    componentWillUnmount() {
        const { unsetPaginationListener } = this.props;
        if(this.query) {
            unsetPaginationListener(this.query);
        }
    }

    _loadData(search = '', isPagination = false) {
        const { navigation, setPaginationListener, feedsByCategoryState } = this.props;
        const pageKey = salePointsByCategoryState ? (isPagination ? 
            feedsByCategoryState.pageKey : null) : null;
        const { category } = navigation.state.params;
        let where = []; let endAt = null;

        if(!isEmpty(search)) {
            where.push(["name", ">=", search.toUpperCase()]);
            endAt = search.toUpperCase() + "\uf8ff";
        } 

        where.push(["type", "==", category]);

        this.query = createQuery(
            "feeds", // Feeds path in firebase
            null, 
            null, 
            null,
            where, 
            pageKey, 
            endAt, 
            PAGINATION_ITEM_PER_PAGE, 
            "feedsByCategory"
        );

        setPaginationListener(this.query, "name", pageKey, isPagination, "ASC");

    }

    _displayFeedDetails = (feedId) => {
        const { navigation } = this.props;
        navigation.navigate("feedDetails", {"feedId": feedId});
    }

    _handleInput = (text) => {
        this.setState({ search: text });
    }

    _handleSubmitSearch() {
        const { search } = this.state;
        if(!isEmpty(search)) {
            this._loadData(search, false);
        }
    }

    _handleRefreshSearch() {
        this.setState({ search: '' }, () => {
            this._loadData();
        });
    }

    _renderItem = ({ item }) => (
        <FeedItem 
            name={item.name}  
            feedId={item.id}
            displayFeedDetails={this._displayFeedDetails}
        />
    );

    _displayFeedsByCategory(feedsByCategoryState, isLoaded, isError, isEmpty) {

        if(!isLoaded) {
            return (
                <View style={{alignItems: 'center', marginTop: 50}}>
                    <Spinner isAbsolute={false} 
                        containerStyle={{marginHorizontal: 5}} 
                    />
                </View>
            );
        } else if(isError) {
            return (
                <View style={{justifyContent: 'center', alignItems: 'center', marginTop: 50}}>
                    <Text style={{color: 'white', fontWeight: 'bold'}}>
                    Erreur de connexion.
                    </Text>
                </View>
            );
        } else if(isEmpty) {
            return (
                <View style={{justifyContent: 'center', alignItems: 'center', marginTop: 50}}>
                    <Text style={{color: 'white', fontWeight: 'bold'}}>
                    Aucun point de vente trouvé.
                    </Text>
                </View>
            );
        } else {

            let feedsByCategory = [];
            let willPaginate = false;

            if(feedsByCategoryState) {
                feedsByCategory = feedsByCategoryState.data;
                willPaginate = feedsByCategoryState.willPaginate;
            }

            return (
                <FlatList
                    contentContainerStyle={styles.list_container}
                    keyExtractor={(item, index) => index.toString()}
                    data={feedsByCategory}
                    renderItem={this._renderItem}
                    onEndReachedThreshold={0.5}
                    onEndReached={() => {
                        if(willPaginate) {
                            this._loadData(this.state.search, true); 
                        }
                    }}
                />
            );
        }

    }

    render() {
        const { navigation, feedsByCategoryState } = this.props;
        const { isLoaded, isError, isEmpty } = getStateRequest(feedsByCategoryState);
        const { image } = navigation.state.params;
        const { search } = this.state;

        return (
            <View style={styles.main_container}>
                <View style={styles.search_wrapper}>
                    <Input
                        inputContainerStyle={{borderBottomWidth: 0, paddingHorizontal: 0}}
                        containerStyle={styles.search_container} 
                        onChangeText={this._handleInput}
                        value={search}
                        leftIcon={
                            <Icon iconStyle={{color: '#D20000'}}
                                containerStyle={{padding: 0}} 
                                type="font-awesome" 
                                name="search-plus" onPress={() => this._handleSubmitSearch()} 
                            />
                        } 
                        rightIcon={
                            <Icon iconStyle={{color: '#D20000'}} 
                                containerStyle={{padding: 0}}
                                type="font-awesome" 
                                name="refresh" onPress={() => this._handleRefreshSearch()} 
                            />
                        }
                        placeholder="Rechercher ..." 
                    />
                    <Image 
                        source={{uri: image}}
                        containerStyle={styles.image_container_style}
                        style={styles.image_style}
                        onError={(error) => {}}
                    />
                </View>

                />
                {
                    this._displayFeedsByCategory(
                        feedsByCategoryState,
                        isLoaded, 
                        isError, 
                        isEmpty
                    )
                }
            </View>
        )
    }
}

const styles = StyleSheet.create({
    main_container: {
        flex: 1,
        paddingBottom: 5,
        backgroundColor: '#D20000'
    },
    search_wrapper: { 
        flexDirection: 'row', 
        alignItems: 'center', 
        marginTop: 0, 
        marginBottom: 0,
        marginHorizontal: 0,
        paddingVertical: "3%",
        backgroundColor: 'white'
    },
    search_container: {
        height: 55,
        borderRadius: 10, 
        borderColor: "#D20000",
        borderWidth: 1,
        marginLeft: "1%", 
        flex: 1
    },
    title_icon_style: {
        fontSize: 13,
        color: "#D20000",
        fontWeight: 'bold'
    },
    title_icon_container_style: {
        marginRight: "1%", 
        flex: 1
    },
    list_container: {
        marginHorizontal: 5,
        marginVertical: 5,
        paddingBottom: 5,
    },
    image_container_style: {
        height: 100,
        width: 100,
        alignItems: "center", 
        backgroundColor: "white"
    },
    image_style: {
        height: 100,
        width: 100
    }
});

const mapStateToProps = (state) => ({
    feedsByCategoryState: state.firestorePaginator.feedsByCategory,
});

export default connect(mapStateToProps, { setPaginationListener, unsetPaginationListener })
(ListFeed);

Это фрагмент кода в проекте, который я реализовал, поэтому отсутствует какой-либо компонент или импорт... Возьмем его в качестве примера.

Это попытка решения, и я знаю, что есть лучший способ сделать это. Пожалуйста, я хотел бы получить совет.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню