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