diff --git a/admin/src/components/events/events-table-virtualized.js b/admin/src/components/events/events-table-virtualized.js index 272ce38..941effc 100644 --- a/admin/src/components/events/events-table-virtualized.js +++ b/admin/src/components/events/events-table-virtualized.js @@ -2,7 +2,7 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import { Table, Column } from 'react-virtualized' import { - fetchAllEvents, + fetchLazyEvents, selectEvent, eventListSelector, loadedSelector, @@ -15,12 +15,12 @@ export class EventsTableVirtualized extends Component { static propTypes = {} componentDidMount() { - this.props.fetchAllEvents() + this.props.fetchLazyEvents() } render() { const { events, loading } = this.props - if (loading) return + if (loading && !events.length) return return ( @@ -43,6 +44,12 @@ export class EventsTableVirtualized extends Component { rowGetter = ({ index }) => this.props.events[index] handleSelect = ({ rowData }) => this.props.selectEvent(rowData.uid) + + handleScroll = ({ stopIndex }) => { + if (stopIndex > this.props.events.length - 3) { + this.props.fetchLazyEvents() + } + } } export default connect( @@ -51,5 +58,5 @@ export default connect( loading: loadingSelector(state), loaded: loadedSelector(state) }), - { fetchAllEvents, selectEvent } + { fetchLazyEvents, selectEvent } )(EventsTableVirtualized) diff --git a/admin/src/components/events/events-table-virtualized.test.js b/admin/src/components/events/events-table-virtualized.test.js new file mode 100644 index 0000000..8c8060f --- /dev/null +++ b/admin/src/components/events/events-table-virtualized.test.js @@ -0,0 +1,21 @@ +import React from 'react' +import Enzyme, { shallow } from 'enzyme' +import Adapter from 'enzyme-adapter-react-16' +import { EventsTableVirtualized } from './events-table-virtualized' +import Loader from '../common/loader' + +Enzyme.configure({ adapter: new Adapter() }) + +describe('EventsTableVirtualized', () => { + it('should render a loader', () => { + const container = shallow( + {}} /> + ) + + expect(container.contains()).toBe(true) + }) + + it('should fetch lazy events', (done) => { + shallow() + }) +}) diff --git a/admin/src/ducks/events.js b/admin/src/ducks/events.js index 2718eed..d87ec01 100644 --- a/admin/src/ducks/events.js +++ b/admin/src/ducks/events.js @@ -10,10 +10,14 @@ import { fbToEntities } from './utils' * */ export const moduleName = 'events' const prefix = `${appName}/${moduleName}` +const fetchLimit = 10 export const FETCH_ALL_REQUEST = `${prefix}/FETCH_ALL_REQUEST` export const FETCH_ALL_START = `${prefix}/FETCH_ALL_START` export const FETCH_ALL_SUCCESS = `${prefix}/FETCH_ALL_SUCCESS` +export const FETCH_LAZY_REQUEST = `${prefix}/FETCH_LAZY_REQUEST` +export const FETCH_LAZY_START = `${prefix}/FETCH_LAZY_START` +export const FETCH_LAZY_SUCCESS = `${prefix}/FETCH_LAZY_SUCCESS` export const TOGGLE_SELECT = `${prefix}/TOGGLE_SELECT` /** @@ -49,6 +53,17 @@ export default function reducer(state = new ReducerRecord(), action) { .set('loaded', true) .set('entities', fbToEntities(payload, EventRecord)) + case FETCH_LAZY_START: + return state.set('loading', true) + + case FETCH_LAZY_SUCCESS: { + const loadedEntities = state.entities.size ? state.entities : null + + return state + .set('loading', false) + .set('entities', fbToEntities(payload, EventRecord, loadedEntities)) + } + case TOGGLE_SELECT: return state.update( 'selected', @@ -84,6 +99,11 @@ export const eventListSelector = createSelector(entitiesSelector, (entities) => entities.toArray() ) +export const lastEventKeySelector = createSelector( + eventListSelector, + (events) => (events.length ? events[events.length - 1].uid : '') +) + export const selectionSelector = createSelector( stateSelector, (state) => state.selected @@ -106,6 +126,12 @@ export function fetchAllEvents() { } } +export function fetchLazyEvents() { + return { + type: FETCH_LAZY_REQUEST + } +} + export const selectEvent = (uid) => ({ type: TOGGLE_SELECT, payload: { uid } @@ -132,6 +158,30 @@ export function* fetchAllSaga() { }) } +export function* fetchLazySaga() { + const eventsState = yield select(stateSelector) + if (eventsState.loading) return + + yield put({ + type: FETCH_LAZY_START + }) + + const fetchStartAt = yield select(lastEventKeySelector) + const ref = firebase + .database() + .ref('events') + .orderByKey() + .limitToFirst(fetchLimit) + .startAt(fetchStartAt) + const snapshot = yield call([ref, ref.once], 'value') + + yield put({ + type: FETCH_LAZY_SUCCESS, + payload: snapshot.val() + }) +} + export function* saga() { yield all([takeEvery(FETCH_ALL_REQUEST, fetchAllSaga)]) + yield all([takeEvery(FETCH_LAZY_REQUEST, fetchLazySaga)]) } diff --git a/admin/src/ducks/utils.js b/admin/src/ducks/utils.js index eb16523..8b292f5 100644 --- a/admin/src/ducks/utils.js +++ b/admin/src/ducks/utils.js @@ -4,10 +4,17 @@ export function generateId() { return Date.now() + Math.random() } -export function fbToEntities(values, DataRecord) { - return new List( +export function fbToEntities(values, DataRecord, initailValues) { + const newList = new List( Object.entries(values).map( ([uid, value]) => new DataRecord({ uid, ...value }) ) ) + + // такой подход для этой утилиты ок? Или лучше выделить новую функцию? + if (initailValues) { + return new List(initailValues).concat(newList) + } + + return newList }