EntityStore: switch all hooks to use a callback function
This commit is contained in:
parent
9d12173b87
commit
a530ec0202
|
@ -1,5 +1,5 @@
|
|||
import type { Entity } from '../types';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type z from 'zod';
|
||||
|
||||
type EntitySchema<TEntity extends Entity = Entity> = z.ZodType<TEntity, z.ZodTypeDef, any>;
|
||||
|
@ -33,9 +33,9 @@ interface EntityCallbacks<Value, Error = unknown> {
|
|||
|
||||
/**
|
||||
* Passed into hooks to make requests.
|
||||
* Can be a URL for GET requests, or a request object.
|
||||
* Must return an Axios response.
|
||||
*/
|
||||
type EntityRequest = string | URL | AxiosRequestConfig;
|
||||
type EntityFn<T> = (value: T) => Promise<AxiosResponse>
|
||||
|
||||
export type {
|
||||
EntitySchema,
|
||||
|
@ -43,5 +43,5 @@ export type {
|
|||
EntitiesPath,
|
||||
EntityPath,
|
||||
EntityCallbacks,
|
||||
EntityRequest,
|
||||
EntityFn,
|
||||
};
|
|
@ -1,36 +1,31 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useLoading } from 'soapbox/hooks';
|
||||
|
||||
import { importEntities } from '../actions';
|
||||
|
||||
import { useEntityRequest } from './useEntityRequest';
|
||||
import { parseEntitiesPath, toAxiosRequest } from './utils';
|
||||
import { parseEntitiesPath } from './utils';
|
||||
|
||||
import type { Entity } from '../types';
|
||||
import type { EntityCallbacks, EntityRequest, EntitySchema, ExpandedEntitiesPath } from './types';
|
||||
import type { EntityCallbacks, EntityFn, EntitySchema, ExpandedEntitiesPath } from './types';
|
||||
|
||||
interface UseCreateEntityOpts<TEntity extends Entity = Entity> {
|
||||
schema?: EntitySchema<TEntity>
|
||||
}
|
||||
|
||||
function useCreateEntity<TEntity extends Entity = Entity, Data = any>(
|
||||
function useCreateEntity<TEntity extends Entity = Entity, Data = unknown>(
|
||||
expandedPath: ExpandedEntitiesPath,
|
||||
entityRequest: EntityRequest,
|
||||
entityFn: EntityFn<Data>,
|
||||
opts: UseCreateEntityOpts<TEntity> = {},
|
||||
) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { request, isLoading } = useEntityRequest();
|
||||
const [isLoading, setPromise] = useLoading();
|
||||
const { entityType, listKey } = parseEntitiesPath(expandedPath);
|
||||
|
||||
async function createEntity(data: Data, callbacks: EntityCallbacks<TEntity> = {}): Promise<void> {
|
||||
try {
|
||||
const result = await request({
|
||||
...toAxiosRequest(entityRequest),
|
||||
data,
|
||||
});
|
||||
|
||||
const result = await setPromise(entityFn(data));
|
||||
const schema = opts.schema || z.custom<TEntity>();
|
||||
const entity = schema.parse(result.data);
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import { useAppDispatch, useGetState } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useGetState, useLoading } from 'soapbox/hooks';
|
||||
|
||||
import { deleteEntities, importEntities } from '../actions';
|
||||
|
||||
import { useEntityRequest } from './useEntityRequest';
|
||||
import { toAxiosRequest } from './utils';
|
||||
|
||||
import type { EntityCallbacks, EntityRequest } from './types';
|
||||
import type { EntityCallbacks, EntityFn } from './types';
|
||||
|
||||
/**
|
||||
* Optimistically deletes an entity from the store.
|
||||
|
@ -14,11 +11,11 @@ import type { EntityCallbacks, EntityRequest } from './types';
|
|||
*/
|
||||
function useDeleteEntity(
|
||||
entityType: string,
|
||||
entityRequest: EntityRequest,
|
||||
entityFn: EntityFn<string>,
|
||||
) {
|
||||
const dispatch = useAppDispatch();
|
||||
const getState = useGetState();
|
||||
const { request, isLoading } = useEntityRequest();
|
||||
const [isLoading, setPromise] = useLoading();
|
||||
|
||||
async function deleteEntity(entityId: string, callbacks: EntityCallbacks<string> = {}): Promise<void> {
|
||||
// Get the entity before deleting, so we can reverse the action if the API request fails.
|
||||
|
@ -28,11 +25,7 @@ function useDeleteEntity(
|
|||
dispatch(deleteEntities([entityId], entityType, { preserveLists: true }));
|
||||
|
||||
try {
|
||||
// HACK: replace occurrences of `:id` in the URL. Maybe there's a better way?
|
||||
const axiosReq = toAxiosRequest(entityRequest);
|
||||
axiosReq.url?.replaceAll(':id', entityId);
|
||||
|
||||
await request(axiosReq);
|
||||
await setPromise(entityFn(entityId));
|
||||
|
||||
// Success - finish deleting entity from the state.
|
||||
dispatch(deleteEntities([entityId], entityType));
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useLoading } from 'soapbox/hooks';
|
||||
|
||||
import { dismissEntities } from '../actions';
|
||||
|
||||
import { parseEntitiesPath } from './utils';
|
||||
|
||||
import type { ExpandedEntitiesPath } from './types';
|
||||
|
||||
type DismissFn<T> = (entityId: string) => Promise<T> | T;
|
||||
import type { EntityFn, ExpandedEntitiesPath } from './types';
|
||||
|
||||
/**
|
||||
* Removes an entity from a specific list.
|
||||
* To remove an entity globally from all lists, see `useDeleteEntity`.
|
||||
*/
|
||||
function useDismissEntity<T = unknown>(expandedPath: ExpandedEntitiesPath, dismissFn: DismissFn<T>) {
|
||||
const { entityType, listKey } = parseEntitiesPath(expandedPath);
|
||||
|
||||
function useDismissEntity(expandedPath: ExpandedEntitiesPath, entityFn: EntityFn<string>) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isLoading, setPromise] = useLoading();
|
||||
const { entityType, listKey } = parseEntitiesPath(expandedPath);
|
||||
|
||||
// TODO: optimistic dismissing
|
||||
return async function dismissEntity(entityId: string): Promise<T> {
|
||||
const result = await dismissFn(entityId);
|
||||
async function dismissEntity(entityId: string) {
|
||||
const result = await setPromise(entityFn(entityId));
|
||||
dispatch(dismissEntities([entityId], entityType, listKey));
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
dismissEntity,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,17 +2,16 @@ import { useEffect } from 'react';
|
|||
import z from 'zod';
|
||||
|
||||
import { getNextLink, getPrevLink } from 'soapbox/api';
|
||||
import { useAppDispatch, useAppSelector, useGetState } from 'soapbox/hooks';
|
||||
import { useApi, useAppDispatch, useAppSelector, useGetState } from 'soapbox/hooks';
|
||||
import { filteredArray } from 'soapbox/schemas/utils';
|
||||
import { realNumberSchema } from 'soapbox/utils/numbers';
|
||||
|
||||
import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess, invalidateEntityList } from '../actions';
|
||||
|
||||
import { useEntityRequest } from './useEntityRequest';
|
||||
import { parseEntitiesPath } from './utils';
|
||||
|
||||
import type { Entity, EntityListState } from '../types';
|
||||
import type { EntitiesPath, EntityRequest, EntitySchema, ExpandedEntitiesPath } from './types';
|
||||
import type { EntitiesPath, EntityFn, EntitySchema, ExpandedEntitiesPath } from './types';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
/** Additional options for the hook. */
|
||||
|
@ -33,11 +32,11 @@ function useEntities<TEntity extends Entity>(
|
|||
/** Tells us where to find/store the entity in the cache. */
|
||||
expandedPath: ExpandedEntitiesPath,
|
||||
/** API route to GET, eg `'/api/v1/notifications'`. If undefined, nothing will be fetched. */
|
||||
entityRequest: EntityRequest,
|
||||
entityFn: EntityFn<void>,
|
||||
/** Additional options for the hook. */
|
||||
opts: UseEntitiesOpts<TEntity> = {},
|
||||
) {
|
||||
const { request } = useEntityRequest();
|
||||
const api = useApi();
|
||||
const dispatch = useAppDispatch();
|
||||
const getState = useGetState();
|
||||
|
||||
|
@ -55,14 +54,14 @@ function useEntities<TEntity extends Entity>(
|
|||
const next = useListState(path, 'next');
|
||||
const prev = useListState(path, 'prev');
|
||||
|
||||
const fetchPage = async(req: EntityRequest, overwrite = false): Promise<void> => {
|
||||
const fetchPage = async(req: EntityFn<void>, overwrite = false): Promise<void> => {
|
||||
// Get `isFetching` state from the store again to prevent race conditions.
|
||||
const isFetching = selectListState(getState(), path, 'fetching');
|
||||
if (isFetching) return;
|
||||
|
||||
dispatch(entitiesFetchRequest(entityType, listKey));
|
||||
try {
|
||||
const response = await request(req);
|
||||
const response = await req();
|
||||
const schema = opts.schema || z.custom<TEntity>();
|
||||
const entities = filteredArray(schema).parse(response.data);
|
||||
const parsedCount = realNumberSchema.safeParse(response.headers['x-total-count']);
|
||||
|
@ -83,18 +82,18 @@ function useEntities<TEntity extends Entity>(
|
|||
};
|
||||
|
||||
const fetchEntities = async(): Promise<void> => {
|
||||
await fetchPage(entityRequest, true);
|
||||
await fetchPage(entityFn, true);
|
||||
};
|
||||
|
||||
const fetchNextPage = async(): Promise<void> => {
|
||||
if (next) {
|
||||
await fetchPage(next);
|
||||
await fetchPage(() => api.get(next));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPreviousPage = async(): Promise<void> => {
|
||||
if (prev) {
|
||||
await fetchPage(prev);
|
||||
await fetchPage(() => api.get(prev));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -113,7 +112,7 @@ function useEntities<TEntity extends Entity>(
|
|||
if (isInvalid || isUnset || isStale) {
|
||||
fetchEntities();
|
||||
}
|
||||
}, [entityRequest, isEnabled]);
|
||||
}, [isEnabled]);
|
||||
|
||||
return {
|
||||
entities,
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import { useEffect } from 'react';
|
||||
import z from 'zod';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useAppSelector, useLoading } from 'soapbox/hooks';
|
||||
|
||||
import { importEntities } from '../actions';
|
||||
|
||||
import { useEntityRequest } from './useEntityRequest';
|
||||
|
||||
import type { Entity } from '../types';
|
||||
import type { EntitySchema, EntityPath, EntityRequest } from './types';
|
||||
import type { EntitySchema, EntityPath, EntityFn } from './types';
|
||||
|
||||
/** Additional options for the hook. */
|
||||
interface UseEntityOpts<TEntity extends Entity> {
|
||||
|
@ -20,10 +18,10 @@ interface UseEntityOpts<TEntity extends Entity> {
|
|||
|
||||
function useEntity<TEntity extends Entity>(
|
||||
path: EntityPath,
|
||||
entityRequest: EntityRequest,
|
||||
entityFn: EntityFn<void>,
|
||||
opts: UseEntityOpts<TEntity> = {},
|
||||
) {
|
||||
const { request, isLoading: isFetching } = useEntityRequest();
|
||||
const [isFetching, setPromise] = useLoading();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [entityType, entityId] = path;
|
||||
|
@ -37,7 +35,7 @@ function useEntity<TEntity extends Entity>(
|
|||
|
||||
const fetchEntity = async () => {
|
||||
try {
|
||||
const response = await request(entityRequest);
|
||||
const response = await setPromise(entityFn());
|
||||
const entity = schema.parse(response.data);
|
||||
dispatch(importEntities([entity], entityType));
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { useApi } from 'soapbox/hooks';
|
||||
|
||||
import { useCreateEntity } from './useCreateEntity';
|
||||
import { useDeleteEntity } from './useDeleteEntity';
|
||||
import { parseEntitiesPath } from './utils';
|
||||
|
@ -19,13 +21,14 @@ function useEntityActions<TEntity extends Entity = Entity, Data = any>(
|
|||
endpoints: EntityActionEndpoints,
|
||||
opts: UseEntityActionsOpts<TEntity> = {},
|
||||
) {
|
||||
const api = useApi();
|
||||
const { entityType, path } = parseEntitiesPath(expandedPath);
|
||||
|
||||
const { deleteEntity, isLoading: deleteLoading } =
|
||||
useDeleteEntity(entityType, { method: 'delete', url: endpoints.delete });
|
||||
useDeleteEntity(entityType, (entityId) => api.delete(endpoints.delete!.replaceAll(':id', entityId)));
|
||||
|
||||
const { createEntity, isLoading: createLoading } =
|
||||
useCreateEntity<TEntity, Data>(path, { method: 'post', url: endpoints.post }, opts);
|
||||
useCreateEntity<TEntity, Data>(path, (data) => api.post(endpoints.post!, data), opts);
|
||||
|
||||
return {
|
||||
createEntity,
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { useApi, useLoading } from 'soapbox/hooks';
|
||||
|
||||
import { EntityRequest } from './types';
|
||||
import { toAxiosRequest } from './utils';
|
||||
|
||||
function useEntityRequest() {
|
||||
const api = useApi();
|
||||
const [isLoading, setPromise] = useLoading();
|
||||
|
||||
function request(entityRequest: EntityRequest) {
|
||||
const req = api.request(toAxiosRequest(entityRequest));
|
||||
return setPromise(req);
|
||||
}
|
||||
|
||||
return {
|
||||
request,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
export { useEntityRequest };
|
|
@ -1,32 +1,36 @@
|
|||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { useAppDispatch, useLoading } from 'soapbox/hooks';
|
||||
|
||||
import { incrementEntities } from '../actions';
|
||||
|
||||
import { parseEntitiesPath } from './utils';
|
||||
|
||||
import type { ExpandedEntitiesPath } from './types';
|
||||
|
||||
type IncrementFn<T> = (entityId: string) => Promise<T> | T;
|
||||
import type { EntityFn, ExpandedEntitiesPath } from './types';
|
||||
|
||||
/**
|
||||
* Increases (or decreases) the `totalCount` in the entity list by the specified amount.
|
||||
* This only works if the API returns an `X-Total-Count` header and your components read it.
|
||||
*/
|
||||
function useIncrementEntity<T = unknown>(
|
||||
function useIncrementEntity(
|
||||
expandedPath: ExpandedEntitiesPath,
|
||||
diff: number,
|
||||
incrementFn: IncrementFn<T>,
|
||||
entityFn: EntityFn<string>,
|
||||
) {
|
||||
const { entityType, listKey } = parseEntitiesPath(expandedPath);
|
||||
const dispatch = useAppDispatch();
|
||||
const [isLoading, setPromise] = useLoading();
|
||||
const { entityType, listKey } = parseEntitiesPath(expandedPath);
|
||||
|
||||
return async function incrementEntity(entityId: string): Promise<void> {
|
||||
async function incrementEntity(entityId: string): Promise<void> {
|
||||
dispatch(incrementEntities(entityType, listKey, diff));
|
||||
try {
|
||||
await incrementFn(entityId);
|
||||
await setPromise(entityFn(entityId));
|
||||
} catch (e) {
|
||||
dispatch(incrementEntities(entityType, listKey, diff * -1));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
incrementEntity,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type { EntitiesPath, EntityRequest, ExpandedEntitiesPath } from './types';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import type { EntitiesPath, ExpandedEntitiesPath } from './types';
|
||||
|
||||
function parseEntitiesPath(expandedPath: ExpandedEntitiesPath) {
|
||||
const [entityType, ...listKeys] = expandedPath;
|
||||
|
@ -13,15 +12,5 @@ function parseEntitiesPath(expandedPath: ExpandedEntitiesPath) {
|
|||
};
|
||||
}
|
||||
|
||||
function toAxiosRequest(req: EntityRequest): AxiosRequestConfig {
|
||||
if (typeof req === 'string' || req instanceof URL) {
|
||||
return {
|
||||
method: 'get',
|
||||
url: req.toString(),
|
||||
};
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
export { parseEntitiesPath, toAxiosRequest };
|
||||
export { parseEntitiesPath };
|
|
@ -11,20 +11,20 @@ function useGroupMembershipRequests(groupId: string) {
|
|||
|
||||
const { entities, invalidate, ...rest } = useEntities(
|
||||
path,
|
||||
`/api/v1/groups/${groupId}/membership_requests`,
|
||||
() => api.get(`/api/v1/groups/${groupId}/membership_requests`),
|
||||
{ schema: accountSchema },
|
||||
);
|
||||
|
||||
const authorize = useIncrementEntity(path, -1, (accountId: string) => {
|
||||
return api
|
||||
.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`)
|
||||
.then(invalidate);
|
||||
const { incrementEntity: authorize } = useIncrementEntity(path, -1, async (accountId: string) => {
|
||||
const response = await api.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/authorize`);
|
||||
invalidate();
|
||||
return response;
|
||||
});
|
||||
|
||||
const reject = useIncrementEntity(path, -1, (accountId: string) => {
|
||||
return api
|
||||
.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`)
|
||||
.then(invalidate);
|
||||
const { incrementEntity: reject } = useIncrementEntity(path, -1, async (accountId: string) => {
|
||||
const response = await api.post(`/api/v1/groups/${groupId}/membership_requests/${accountId}/reject`);
|
||||
invalidate();
|
||||
return response;
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
@ -2,10 +2,14 @@ import { Entities } from 'soapbox/entity-store/entities';
|
|||
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||
import { GroupMember, groupMemberSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useApi } from '../useApi';
|
||||
|
||||
function useGroupMembers(groupId: string, role: string) {
|
||||
const api = useApi();
|
||||
|
||||
const { entities, ...result } = useEntities<GroupMember>(
|
||||
[Entities.GROUP_MEMBERSHIPS, groupId, role],
|
||||
`/api/v1/groups/${groupId}/memberships?role=${role}`,
|
||||
() => api.get(`/api/v1/groups/${groupId}/memberships?role=${role}`),
|
||||
{ schema: groupMemberSchema },
|
||||
);
|
||||
|
||||
|
|
|
@ -2,15 +2,17 @@ import { Entities } from 'soapbox/entity-store/entities';
|
|||
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||
import { Group, groupSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useApi } from '../useApi';
|
||||
import { useFeatures } from '../useFeatures';
|
||||
import { useGroupRelationships } from '../useGroups';
|
||||
|
||||
function usePopularGroups() {
|
||||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
|
||||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS, 'popular'],
|
||||
'/api/mock/groups', // '/api/v1/truth/trends/groups'
|
||||
() => api.get('/api/mock/groups'), // '/api/v1/truth/trends/groups'
|
||||
{
|
||||
schema: groupSchema,
|
||||
enabled: features.groupsDiscovery,
|
||||
|
|
|
@ -2,15 +2,17 @@ import { Entities } from 'soapbox/entity-store/entities';
|
|||
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||
import { Group, groupSchema } from 'soapbox/schemas';
|
||||
|
||||
import { useApi } from '../useApi';
|
||||
import { useFeatures } from '../useFeatures';
|
||||
import { useGroupRelationships } from '../useGroups';
|
||||
|
||||
function useSuggestedGroups() {
|
||||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
|
||||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS, 'suggested'],
|
||||
'/api/mock/groups', // '/api/v1/truth/suggestions/groups'
|
||||
() => api.get('/api/mock/groups'), // '/api/v1/truth/suggestions/groups'
|
||||
{
|
||||
schema: groupSchema,
|
||||
enabled: features.groupsDiscovery,
|
||||
|
|
|
@ -2,17 +2,19 @@ import { z } from 'zod';
|
|||
|
||||
import { Entities } from 'soapbox/entity-store/entities';
|
||||
import { useEntities, useEntity } from 'soapbox/entity-store/hooks';
|
||||
import { useApi } from 'soapbox/hooks';
|
||||
import { groupSchema, Group } from 'soapbox/schemas/group';
|
||||
import { groupRelationshipSchema, GroupRelationship } from 'soapbox/schemas/group-relationship';
|
||||
|
||||
import { useFeatures } from './useFeatures';
|
||||
|
||||
function useGroups() {
|
||||
const api = useApi();
|
||||
const features = useFeatures();
|
||||
|
||||
const { entities, ...result } = useEntities<Group>(
|
||||
[Entities.GROUPS],
|
||||
'/api/v1/groups',
|
||||
() => api.get('/api/v1/groups'),
|
||||
{ enabled: features.groups, schema: groupSchema },
|
||||
);
|
||||
const { relationships } = useGroupRelationships(entities.map(entity => entity.id));
|
||||
|
@ -29,9 +31,11 @@ function useGroups() {
|
|||
}
|
||||
|
||||
function useGroup(groupId: string, refetch = true) {
|
||||
const api = useApi();
|
||||
|
||||
const { entity: group, ...result } = useEntity<Group>(
|
||||
[Entities.GROUPS, groupId],
|
||||
`/api/v1/groups/${groupId}`,
|
||||
() => api.get(`/api/v1/groups/${groupId}`),
|
||||
{ schema: groupSchema, refetch },
|
||||
);
|
||||
const { entity: relationship } = useGroupRelationship(groupId);
|
||||
|
@ -43,18 +47,21 @@ function useGroup(groupId: string, refetch = true) {
|
|||
}
|
||||
|
||||
function useGroupRelationship(groupId: string) {
|
||||
const api = useApi();
|
||||
|
||||
return useEntity<GroupRelationship>(
|
||||
[Entities.GROUP_RELATIONSHIPS, groupId],
|
||||
`/api/v1/groups/relationships?id[]=${groupId}`,
|
||||
() => api.get(`/api/v1/groups/relationships?id[]=${groupId}`),
|
||||
{ schema: z.array(groupRelationshipSchema).transform(arr => arr[0]) },
|
||||
);
|
||||
}
|
||||
|
||||
function useGroupRelationships(groupIds: string[]) {
|
||||
const api = useApi();
|
||||
const q = groupIds.map(id => `id[]=${id}`).join('&');
|
||||
const { entities, ...result } = useEntities<GroupRelationship>(
|
||||
[Entities.GROUP_RELATIONSHIPS, ...groupIds],
|
||||
`/api/v1/groups/relationships?${q}`,
|
||||
() => api.get(`/api/v1/groups/relationships?${q}`),
|
||||
{ schema: groupRelationshipSchema, enabled: groupIds.length > 0 },
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue