EntityStore: support query invalidation
This commit is contained in:
parent
a256665aad
commit
e2510489c5
5 changed files with 41 additions and 3 deletions
|
@ -6,6 +6,7 @@ const ENTITIES_DISMISS = 'ENTITIES_DISMISS' as const;
|
|||
const ENTITIES_FETCH_REQUEST = 'ENTITIES_FETCH_REQUEST' as const;
|
||||
const ENTITIES_FETCH_SUCCESS = 'ENTITIES_FETCH_SUCCESS' as const;
|
||||
const ENTITIES_FETCH_FAIL = 'ENTITIES_FETCH_FAIL' as const;
|
||||
const ENTITIES_INVALIDATE_LIST = 'ENTITIES_INVALIDATE_LIST' as const;
|
||||
|
||||
/** Action to import entities into the cache. */
|
||||
function importEntities(entities: Entity[], entityType: string, listKey?: string) {
|
||||
|
@ -66,6 +67,14 @@ function entitiesFetchFail(entityType: string, listKey: string | undefined, erro
|
|||
};
|
||||
}
|
||||
|
||||
function invalidateEntityList(entityType: string, listKey: string) {
|
||||
return {
|
||||
type: ENTITIES_INVALIDATE_LIST,
|
||||
entityType,
|
||||
listKey,
|
||||
};
|
||||
}
|
||||
|
||||
/** Any action pertaining to entities. */
|
||||
type EntityAction =
|
||||
ReturnType<typeof importEntities>
|
||||
|
@ -73,7 +82,8 @@ type EntityAction =
|
|||
| ReturnType<typeof dismissEntities>
|
||||
| ReturnType<typeof entitiesFetchRequest>
|
||||
| ReturnType<typeof entitiesFetchSuccess>
|
||||
| ReturnType<typeof entitiesFetchFail>;
|
||||
| ReturnType<typeof entitiesFetchFail>
|
||||
| ReturnType<typeof invalidateEntityList>;
|
||||
|
||||
export {
|
||||
ENTITIES_IMPORT,
|
||||
|
@ -82,12 +92,14 @@ export {
|
|||
ENTITIES_FETCH_REQUEST,
|
||||
ENTITIES_FETCH_SUCCESS,
|
||||
ENTITIES_FETCH_FAIL,
|
||||
ENTITIES_INVALIDATE_LIST,
|
||||
importEntities,
|
||||
deleteEntities,
|
||||
dismissEntities,
|
||||
entitiesFetchRequest,
|
||||
entitiesFetchSuccess,
|
||||
entitiesFetchFail,
|
||||
invalidateEntityList,
|
||||
EntityAction,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { getNextLink, getPrevLink } from 'soapbox/api';
|
|||
import { useApi, useAppDispatch, useAppSelector, useGetState } from 'soapbox/hooks';
|
||||
import { filteredArray } from 'soapbox/schemas/utils';
|
||||
|
||||
import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess } from '../actions';
|
||||
import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess, invalidateEntityList } from '../actions';
|
||||
|
||||
import { parseEntitiesPath } from './utils';
|
||||
|
||||
|
@ -48,6 +48,7 @@ function useEntities<TEntity extends Entity>(
|
|||
const isFetched = useListState(path, 'fetched');
|
||||
const isError = !!useListState(path, 'error');
|
||||
const totalCount = useListState(path, 'totalCount');
|
||||
const isInvalid = useListState(path, 'invalid');
|
||||
|
||||
const next = useListState(path, 'next');
|
||||
const prev = useListState(path, 'prev');
|
||||
|
@ -72,6 +73,7 @@ function useEntities<TEntity extends Entity>(
|
|||
fetched: true,
|
||||
error: null,
|
||||
lastFetchedAt: new Date(),
|
||||
invalid: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
dispatch(entitiesFetchFail(entityType, listKey, error));
|
||||
|
@ -96,10 +98,19 @@ function useEntities<TEntity extends Entity>(
|
|||
}
|
||||
};
|
||||
|
||||
const invalidate = () => {
|
||||
dispatch(invalidateEntityList(entityType, listKey));
|
||||
};
|
||||
|
||||
const staleTime = opts.staleTime ?? 60000;
|
||||
|
||||
useEffect(() => {
|
||||
if (isEnabled && !isFetching && (!lastFetchedAt || lastFetchedAt.getTime() + staleTime <= Date.now())) {
|
||||
if (!isEnabled) return;
|
||||
if (isFetching) return;
|
||||
const isUnset = !lastFetchedAt;
|
||||
const isStale = lastFetchedAt ? Date.now() >= lastFetchedAt.getTime() + staleTime : false;
|
||||
|
||||
if (isInvalid || isUnset || isStale) {
|
||||
fetchEntities();
|
||||
}
|
||||
}, [endpoint, isEnabled]);
|
||||
|
@ -116,6 +127,7 @@ function useEntities<TEntity extends Entity>(
|
|||
isFetched,
|
||||
isFetching,
|
||||
isLoading: isFetching && entities.length === 0,
|
||||
invalidate,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
ENTITIES_FETCH_SUCCESS,
|
||||
ENTITIES_FETCH_FAIL,
|
||||
EntityAction,
|
||||
ENTITIES_INVALIDATE_LIST,
|
||||
} from './actions';
|
||||
import { createCache, createList, updateStore, updateList } from './utils';
|
||||
|
||||
|
@ -114,6 +115,14 @@ const setFetching = (
|
|||
});
|
||||
};
|
||||
|
||||
const invalidateEntityList = (state: State, entityType: string, listKey: string) => {
|
||||
return produce(state, draft => {
|
||||
const cache = draft[entityType] ?? createCache();
|
||||
const list = cache.lists[listKey] ?? createList();
|
||||
list.state.invalid = true;
|
||||
});
|
||||
};
|
||||
|
||||
/** Stores various entity data and lists in a one reducer. */
|
||||
function reducer(state: Readonly<State> = {}, action: EntityAction): State {
|
||||
switch (action.type) {
|
||||
|
@ -129,6 +138,8 @@ function reducer(state: Readonly<State> = {}, action: EntityAction): State {
|
|||
return setFetching(state, action.entityType, action.listKey, true);
|
||||
case ENTITIES_FETCH_FAIL:
|
||||
return setFetching(state, action.entityType, action.listKey, false, action.error);
|
||||
case ENTITIES_INVALIDATE_LIST:
|
||||
return invalidateEntityList(state, action.entityType, action.listKey);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ interface EntityListState {
|
|||
fetching: boolean
|
||||
/** Date of the last API fetch for this list. */
|
||||
lastFetchedAt: Date | undefined
|
||||
/** Whether the entities should be refetched on the next component mount. */
|
||||
invalid: boolean
|
||||
}
|
||||
|
||||
/** Cache data pertaining to a paritcular entity type.. */
|
||||
|
|
|
@ -43,6 +43,7 @@ const createListState = (): EntityListState => ({
|
|||
fetched: false,
|
||||
fetching: false,
|
||||
lastFetchedAt: undefined,
|
||||
invalid: false,
|
||||
});
|
||||
|
||||
export {
|
||||
|
|
Loading…
Reference in a new issue