From f7bfc40b70ddba7f5b1ed76920fd7f91db3cd20d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 4 Dec 2022 17:53:56 -0600 Subject: [PATCH] EntityStore: proper pagination support --- app/soapbox/api/index.ts | 4 ++ app/soapbox/entity-store/actions.ts | 8 ++-- app/soapbox/entity-store/hooks/useEntities.ts | 45 ++++++++++++------- app/soapbox/entity-store/reducer.ts | 14 ++++-- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/app/soapbox/api/index.ts b/app/soapbox/api/index.ts index 97d7d25d7..46319cc96 100644 --- a/app/soapbox/api/index.ts +++ b/app/soapbox/api/index.ts @@ -29,6 +29,10 @@ export const getNextLink = (response: AxiosResponse): string | undefined => { return getLinks(response).refs.find(link => link.rel === 'next')?.uri; }; +export const getPrevLink = (response: AxiosResponse): string | undefined => { + return getLinks(response).refs.find(link => link.rel === 'prev')?.uri; +}; + const getToken = (state: RootState, authType: string) => { return authType === 'app' ? getAppToken(state) : getAccessToken(state); }; diff --git a/app/soapbox/entity-store/actions.ts b/app/soapbox/entity-store/actions.ts index e09191f87..d5e78c1f9 100644 --- a/app/soapbox/entity-store/actions.ts +++ b/app/soapbox/entity-store/actions.ts @@ -1,4 +1,4 @@ -import type { Entity } from './types'; +import type { Entity, EntityListState } from './types'; const ENTITIES_IMPORT = 'ENTITIES_IMPORT' as const; const ENTITIES_FETCH_REQUEST = 'ENTITIES_FETCH_REQUEST' as const; @@ -23,20 +23,22 @@ function entitiesFetchRequest(entityType: string, listKey?: string) { }; } -function entitiesFetchSuccess(entities: Entity[], entityType: string, listKey?: string) { +function entitiesFetchSuccess(entities: Entity[], entityType: string, listKey?: string, newState?: EntityListState) { return { type: ENTITIES_FETCH_SUCCESS, entityType, entities, listKey, + newState, }; } -function entitiesFetchFail(entityType: string, listKey?: string) { +function entitiesFetchFail(entityType: string, listKey: string | undefined, error: any) { return { type: ENTITIES_FETCH_FAIL, entityType, listKey, + error, }; } diff --git a/app/soapbox/entity-store/hooks/useEntities.ts b/app/soapbox/entity-store/hooks/useEntities.ts index 89afc23e3..10abc0d4d 100644 --- a/app/soapbox/entity-store/hooks/useEntities.ts +++ b/app/soapbox/entity-store/hooks/useEntities.ts @@ -1,12 +1,13 @@ +import { getNextLink, getPrevLink } from 'soapbox/api'; import { useApi, useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import { importEntities } from '../actions'; +import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess } from '../actions'; import type { Entity } from '../types'; type EntityPath = [entityType: string, listKey: string] -function useEntities(path: EntityPath, url: string) { +function useEntities(path: EntityPath, endpoint: string) { const api = useApi(); const dispatch = useAppDispatch(); @@ -32,26 +33,38 @@ function useEntities(path: EntityPath, url: string) { const hasNextPage = Boolean(list?.state.next); const hasPreviousPage = Boolean(list?.state.prev); - const fetchEntities = async() => { - const { data } = await api.get(url); - dispatch(importEntities(data, entityType, listKey)); - }; - - const fetchNextPage = async() => { - const next = list?.state.next; - - if (next) { - const { data } = await api.get(next); - dispatch(importEntities(data, entityType, listKey)); + const fetchPage = async(url: string): Promise => { + dispatch(entitiesFetchRequest(entityType, listKey)); + try { + const response = await api.get(url); + dispatch(entitiesFetchSuccess(response.data, entityType, listKey, { + next: getNextLink(response), + prev: getPrevLink(response), + fetching: false, + error: null, + })); + } catch (error) { + dispatch(entitiesFetchFail(entityType, listKey, error)); } }; - const fetchPreviousPage = async() => { + const fetchEntities = async(): Promise => { + await fetchPage(endpoint); + }; + + const fetchNextPage = async(): Promise => { + const next = list?.state.next; + + if (next) { + await fetchPage(next); + } + }; + + const fetchPreviousPage = async(): Promise => { const prev = list?.state.prev; if (prev) { - const { data } = await api.get(prev); - dispatch(importEntities(data, entityType, listKey)); + await fetchPage(prev); } }; diff --git a/app/soapbox/entity-store/reducer.ts b/app/soapbox/entity-store/reducer.ts index c588fd3ea..90170d944 100644 --- a/app/soapbox/entity-store/reducer.ts +++ b/app/soapbox/entity-store/reducer.ts @@ -9,7 +9,7 @@ import { } from './actions'; import { createCache, createList, updateStore, updateList } from './utils'; -import type { Entity, EntityCache } from './types'; +import type { Entity, EntityCache, EntityListState } from './types'; enableMapSet(); @@ -22,14 +22,19 @@ const importEntities = ( entityType: string, entities: Entity[], listKey?: string, + newState?: EntityListState, ): State => { return produce(state, draft => { const cache = draft.get(entityType) ?? createCache(); cache.store = updateStore(cache.store, entities); if (listKey) { - const list = cache.lists.get(listKey) ?? createList(); - cache.lists.set(listKey, updateList(list, entities)); + let list = cache.lists.get(listKey) ?? createList(); + list = updateList(list, entities); + if (newState) { + list.state = newState; + } + cache.lists.set(listKey, list); } return draft.set(entityType, cache); @@ -59,8 +64,9 @@ const setFetching = ( function reducer(state: Readonly = new Map(), action: EntityAction): State { switch (action.type) { case ENTITIES_IMPORT: - case ENTITIES_FETCH_SUCCESS: return importEntities(state, action.entityType, action.entities, action.listKey); + case ENTITIES_FETCH_SUCCESS: + return importEntities(state, action.entityType, action.entities, action.listKey, action.newState); case ENTITIES_FETCH_REQUEST: return setFetching(state, action.entityType, action.listKey, true); case ENTITIES_FETCH_FAIL: