EntityStore: support query invalidation

This commit is contained in:
Alex Gleason 2023-03-22 18:17:28 -05:00
parent a256665aad
commit e2510489c5
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
5 changed files with 41 additions and 3 deletions

View file

@ -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,
};

View file

@ -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,
};
}

View file

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

View file

@ -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.. */

View file

@ -43,6 +43,7 @@ const createListState = (): EntityListState => ({
fetched: false,
fetching: false,
lastFetchedAt: undefined,
invalid: false,
});
export {