EntityStore: allow passing an EntityRequest object to useEntities

This commit is contained in:
Alex Gleason 2023-03-23 14:14:53 -05:00
parent 948d66bcab
commit ad3f8acbe5
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
5 changed files with 32 additions and 14 deletions

View File

@ -15,6 +15,7 @@ import { getAccessToken, getAppToken, isURL, parseBaseURL } from 'soapbox/utils/
import type MockAdapter from 'axios-mock-adapter'; import type MockAdapter from 'axios-mock-adapter';
/** /**
Parse Link headers, mostly for pagination. Parse Link headers, mostly for pagination.
@see {@link https://www.npmjs.com/package/http-link-header} @see {@link https://www.npmjs.com/package/http-link-header}

View File

@ -1,4 +1,5 @@
import type { Entity } from '../types'; import type { Entity } from '../types';
import type { AxiosRequestConfig } from 'axios';
import type z from 'zod'; import type z from 'zod';
type EntitySchema<TEntity extends Entity = Entity> = z.ZodType<TEntity, z.ZodTypeDef, any>; type EntitySchema<TEntity extends Entity = Entity> = z.ZodType<TEntity, z.ZodTypeDef, any>;
@ -24,9 +25,16 @@ type EntitiesPath = [entityType: string, listKey: string]
/** Used to look up a single entity by its ID. */ /** Used to look up a single entity by its ID. */
type EntityPath = [entityType: string, entityId: string] type EntityPath = [entityType: string, entityId: string]
/**
* Passed into hooks to make requests.
* Can be a URL for GET requests, or a request object.
*/
type EntityRequest = string | URL | AxiosRequestConfig;
export type { export type {
EntitySchema, EntitySchema,
ExpandedEntitiesPath, ExpandedEntitiesPath,
EntitiesPath, EntitiesPath,
EntityPath, EntityPath,
EntityRequest,
}; };

View File

@ -8,10 +8,10 @@ import { realNumberSchema } from 'soapbox/utils/numbers';
import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess, invalidateEntityList } from '../actions'; import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess, invalidateEntityList } from '../actions';
import { parseEntitiesPath } from './utils'; import { parseEntitiesPath, toAxiosRequest } from './utils';
import type { Entity, EntityListState } from '../types'; import type { Entity, EntityListState } from '../types';
import type { EntitiesPath, EntitySchema, ExpandedEntitiesPath } from './types'; import type { EntitiesPath, EntityRequest, EntitySchema, ExpandedEntitiesPath } from './types';
import type { RootState } from 'soapbox/store'; import type { RootState } from 'soapbox/store';
/** Additional options for the hook. */ /** Additional options for the hook. */
@ -32,7 +32,7 @@ function useEntities<TEntity extends Entity>(
/** Tells us where to find/store the entity in the cache. */ /** Tells us where to find/store the entity in the cache. */
expandedPath: ExpandedEntitiesPath, expandedPath: ExpandedEntitiesPath,
/** API route to GET, eg `'/api/v1/notifications'`. If undefined, nothing will be fetched. */ /** API route to GET, eg `'/api/v1/notifications'`. If undefined, nothing will be fetched. */
endpoint: string | undefined, request: EntityRequest,
/** Additional options for the hook. */ /** Additional options for the hook. */
opts: UseEntitiesOpts<TEntity> = {}, opts: UseEntitiesOpts<TEntity> = {},
) { ) {
@ -54,14 +54,14 @@ function useEntities<TEntity extends Entity>(
const next = useListState(path, 'next'); const next = useListState(path, 'next');
const prev = useListState(path, 'prev'); const prev = useListState(path, 'prev');
const fetchPage = async(url: string, overwrite = false): Promise<void> => { const fetchPage = async(req: EntityRequest, overwrite = false): Promise<void> => {
// Get `isFetching` state from the store again to prevent race conditions. // Get `isFetching` state from the store again to prevent race conditions.
const isFetching = selectListState(getState(), path, 'fetching'); const isFetching = selectListState(getState(), path, 'fetching');
if (isFetching) return; if (isFetching) return;
dispatch(entitiesFetchRequest(entityType, listKey)); dispatch(entitiesFetchRequest(entityType, listKey));
try { try {
const response = await api.get(url); const response = await api.request(toAxiosRequest(req));
const schema = opts.schema || z.custom<TEntity>(); const schema = opts.schema || z.custom<TEntity>();
const entities = filteredArray(schema).parse(response.data); const entities = filteredArray(schema).parse(response.data);
const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']); const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']);
@ -82,9 +82,7 @@ function useEntities<TEntity extends Entity>(
}; };
const fetchEntities = async(): Promise<void> => { const fetchEntities = async(): Promise<void> => {
if (endpoint) { await fetchPage(request, true);
await fetchPage(endpoint, true);
}
}; };
const fetchNextPage = async(): Promise<void> => { const fetchNextPage = async(): Promise<void> => {
@ -114,7 +112,7 @@ function useEntities<TEntity extends Entity>(
if (isInvalid || isUnset || isStale) { if (isInvalid || isUnset || isStale) {
fetchEntities(); fetchEntities();
} }
}, [endpoint, isEnabled]); }, [request, isEnabled]);
return { return {
entities, entities,

View File

@ -1,4 +1,5 @@
import type { EntitiesPath, ExpandedEntitiesPath } from './types'; import type { EntitiesPath, EntityRequest, ExpandedEntitiesPath } from './types';
import type { AxiosRequestConfig } from 'axios';
function parseEntitiesPath(expandedPath: ExpandedEntitiesPath) { function parseEntitiesPath(expandedPath: ExpandedEntitiesPath) {
const [entityType, ...listKeys] = expandedPath; const [entityType, ...listKeys] = expandedPath;
@ -12,4 +13,15 @@ function parseEntitiesPath(expandedPath: ExpandedEntitiesPath) {
}; };
} }
export { parseEntitiesPath }; function toAxiosRequest(req: EntityRequest): AxiosRequestConfig {
if (typeof req === 'string' || req instanceof URL) {
return {
method: 'get',
url: req.toString(),
};
}
return req;
}
export { parseEntitiesPath, toAxiosRequest };

View File

@ -52,11 +52,10 @@ function useGroupRelationship(groupId: string) {
function useGroupRelationships(groupIds: string[]) { function useGroupRelationships(groupIds: string[]) {
const q = groupIds.map(id => `id[]=${id}`).join('&'); const q = groupIds.map(id => `id[]=${id}`).join('&');
const endpoint = groupIds.length ? `/api/v1/groups/relationships?${q}` : undefined;
const { entities, ...result } = useEntities<GroupRelationship>( const { entities, ...result } = useEntities<GroupRelationship>(
[Entities.GROUP_RELATIONSHIPS, ...groupIds], [Entities.GROUP_RELATIONSHIPS, ...groupIds],
endpoint, `/api/v1/groups/relationships?${q}`,
{ schema: groupRelationshipSchema }, { schema: groupRelationshipSchema, enabled: groupIds.length > 0 },
); );
const relationships = entities.reduce<Record<string, GroupRelationship>>((map, relationship) => { const relationships = entities.reduce<Record<string, GroupRelationship>>((map, relationship) => {