pl-fe: batch relationships requests
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -71,6 +71,7 @@
|
||||
"@twemoji/svg": "^15.0.0",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@yornaath/batshit": "^0.11.1",
|
||||
"abortcontroller-polyfill": "^1.7.8",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"blurhash": "^2.0.5",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { type CreateAccountParams, type Relationship } from 'pl-api';
|
||||
|
||||
import { batcher } from 'pl-fe/api/batcher';
|
||||
import { queryClient } from 'pl-fe/queries/client';
|
||||
import { selectAccount } from 'pl-fe/selectors';
|
||||
import { isLoggedIn } from 'pl-fe/utils/auth';
|
||||
@ -85,7 +86,9 @@ const fetchRelationships = (accountIds: string[]) =>
|
||||
return null;
|
||||
}
|
||||
|
||||
return getClient(getState()).accounts.getRelationships(newAccountIds)
|
||||
const fetcher = batcher.relationships(getClient(getState())).fetch;
|
||||
|
||||
return Promise.all(newAccountIds.map(fetcher))
|
||||
.then(response => dispatch(importEntities({ relationships: response })));
|
||||
};
|
||||
|
||||
|
||||
16
packages/pl-fe/src/api/batcher.ts
Normal file
16
packages/pl-fe/src/api/batcher.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { bufferScheduler, create, keyResolver } from '@yornaath/batshit';
|
||||
import memoize from 'lodash/memoize';
|
||||
|
||||
import type { PlApiClient } from 'pl-api';
|
||||
|
||||
const relationships = memoize((client: PlApiClient) => create({
|
||||
fetcher: (ids: string[]) => client.accounts.getRelationships(ids),
|
||||
resolver: keyResolver('id'),
|
||||
scheduler: bufferScheduler(200),
|
||||
}));
|
||||
|
||||
const batcher = {
|
||||
relationships,
|
||||
};
|
||||
|
||||
export { batcher };
|
||||
@ -1,6 +1,7 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, type AccountsAction } from 'pl-fe/actions/accounts';
|
||||
import { batcher } from 'pl-fe/api/batcher';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
|
||||
@ -34,7 +35,7 @@ const useRelationshipQuery = (accountId?: string) => {
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['accountRelationships', accountId],
|
||||
queryFn: () => client.accounts.getRelationships([accountId!]).then(arr => arr[0]),
|
||||
queryFn: () => batcher.relationships(client).fetch(accountId!).then((data) => data || undefined),
|
||||
enabled: isLoggedIn && !!accountId,
|
||||
});
|
||||
};
|
||||
|
||||
@ -4,6 +4,7 @@ import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse,
|
||||
import * as v from 'valibot';
|
||||
|
||||
import { importEntities } from 'pl-fe/actions/importer';
|
||||
import { batcher } from 'pl-fe/api/batcher';
|
||||
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
|
||||
import { useStatContext } from 'pl-fe/contexts/stat-context';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
@ -17,7 +18,6 @@ import { flattenPages, updatePageItem } from 'pl-fe/utils/queries';
|
||||
|
||||
import { useRelationshipQuery } from './accounts/use-relationship';
|
||||
import { queryClient } from './client';
|
||||
import { useFetchRelationships } from './relationships';
|
||||
|
||||
const ChatKeys = {
|
||||
chat: (chatId?: string) => ['chats', 'chat', chatId] as const,
|
||||
@ -60,7 +60,6 @@ const useChats = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const { setUnreadChatsCount } = useStatContext();
|
||||
const fetchRelationships = useFetchRelationships();
|
||||
const { me } = useLoggedIn();
|
||||
|
||||
const getChats = async (pageParam?: Pick<PaginatedResponse<Chat>, 'next'>): Promise<PaginatedResponse<Chat>> => {
|
||||
@ -70,7 +69,8 @@ const useChats = () => {
|
||||
setUnreadChatsCount(sumBy(data, (chat) => chat.unread));
|
||||
|
||||
// Set the relationships to these users in the redux store.
|
||||
fetchRelationships.mutate({ accountIds: items.map((item) => item.account.id) });
|
||||
const fetcher = batcher.relationships(client).fetch;
|
||||
items.map((item) => item.account.id).forEach(fetcher);
|
||||
dispatch(importEntities({ accounts: items.map((item) => item.account) }));
|
||||
|
||||
return response;
|
||||
@ -101,13 +101,11 @@ const useChats = () => {
|
||||
const useChat = (chatId?: string) => {
|
||||
const client = useClient();
|
||||
const dispatch = useAppDispatch();
|
||||
const fetchRelationships = useFetchRelationships();
|
||||
|
||||
const getChat = async () => {
|
||||
if (chatId) {
|
||||
const data = await client.chats.getChat(chatId);
|
||||
|
||||
fetchRelationships.mutate({ accountIds: [data.account.id] });
|
||||
dispatch(importEntities({ accounts: [data.account] }));
|
||||
|
||||
return data;
|
||||
|
||||
@ -1,107 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { __stub } from 'pl-fe/api';
|
||||
import { buildRelationship } from 'pl-fe/jest/factory';
|
||||
import { createTestStore, queryClient, renderHook, rootState, waitFor } from 'pl-fe/jest/test-helpers';
|
||||
import { Store } from 'pl-fe/store';
|
||||
|
||||
import { useFetchRelationships } from './relationships';
|
||||
|
||||
describe('useFetchRelationships()', () => {
|
||||
let store: Store;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState;
|
||||
store = createTestStore(state);
|
||||
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
describe('with a successful query', () => {
|
||||
describe('with one relationship', () => {
|
||||
const id = '123';
|
||||
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock
|
||||
.onGet(`/api/v1/accounts/relationships?id[]=${id}`)
|
||||
.reply(200, [buildRelationship({ id, blocked_by: true })]);
|
||||
});
|
||||
});
|
||||
|
||||
it('is successful', async() => {
|
||||
renderHook(() => {
|
||||
const fetchRelationships = useFetchRelationships();
|
||||
|
||||
useEffect(() => {
|
||||
fetchRelationships.mutate({ accountIds: [id] });
|
||||
}, []);
|
||||
|
||||
return fetchRelationships;
|
||||
}, undefined, store);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(store.getState().relationships.size).toBe(1);
|
||||
expect(store.getState().relationships.getIn([id, 'id'])).toBe(id);
|
||||
expect(store.getState().relationships.getIn([id, 'blocked_by'])).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with multiple relationships', () => {
|
||||
const ids = ['123', '456'];
|
||||
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock
|
||||
.onGet(`/api/v1/accounts/relationships?id[]=${ids[0]}&id[]=${ids[1]}`)
|
||||
.reply(200, ids.map((id) => buildRelationship({ id, blocked_by: true })));
|
||||
});
|
||||
});
|
||||
|
||||
it('is successful', async() => {
|
||||
renderHook(() => {
|
||||
const fetchRelationships = useFetchRelationships();
|
||||
|
||||
useEffect(() => {
|
||||
fetchRelationships.mutate({ accountIds: ids });
|
||||
}, []);
|
||||
|
||||
return fetchRelationships;
|
||||
}, undefined, store);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(store.getState().relationships.size).toBe(2);
|
||||
expect(store.getState().relationships.getIn([ids[0], 'id'])).toBe(ids[0]);
|
||||
expect(store.getState().relationships.getIn([ids[1], 'id'])).toBe(ids[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful query', () => {
|
||||
const id = '123';
|
||||
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet(`/api/v1/accounts/relationships?id[]=${id}`).networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('is successful', async() => {
|
||||
const { result } = renderHook(() => {
|
||||
const fetchRelationships = useFetchRelationships();
|
||||
|
||||
useEffect(() => {
|
||||
fetchRelationships.mutate({ accountIds: [id] });
|
||||
}, []);
|
||||
|
||||
return fetchRelationships;
|
||||
}, undefined, store);
|
||||
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,14 +0,0 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useClient } from 'pl-fe/hooks/use-client';
|
||||
|
||||
const useFetchRelationships = () => {
|
||||
const client = useClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ accountIds }: { accountIds: string[]}) =>
|
||||
client.accounts.getRelationships(accountIds),
|
||||
});
|
||||
};
|
||||
|
||||
export { useFetchRelationships };
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@ -214,6 +214,9 @@ importers:
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.4
|
||||
version: 4.7.0(vite@5.4.21(@types/node@22.17.0)(sass@1.89.2)(terser@5.44.0))
|
||||
'@yornaath/batshit':
|
||||
specifier: ^0.11.1
|
||||
version: 0.11.1
|
||||
abortcontroller-polyfill:
|
||||
specifier: ^1.7.8
|
||||
version: 1.7.8
|
||||
@ -2899,6 +2902,12 @@ packages:
|
||||
'@xtuc/long@4.2.2':
|
||||
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
|
||||
|
||||
'@yornaath/batshit-devtools@1.7.1':
|
||||
resolution: {integrity: sha512-AyttV1Njj5ug+XqEWY1smV45dTWMlWKtj1B8jcFYgBKUFyUlF/qEhD+iP1E5UaRYW6hQRYD9T2WNDwFTrOMWzQ==}
|
||||
|
||||
'@yornaath/batshit@0.11.1':
|
||||
resolution: {integrity: sha512-LYsrHbsdGivKIr2IDcwaqrEYoW2W27a68GT4eW1jlM7nHO3KtH2qItqaOD57wiiOpYzxFRTVz7LUZMVMSfaekQ==}
|
||||
|
||||
abortcontroller-polyfill@1.7.8:
|
||||
resolution: {integrity: sha512-9f1iZ2uWh92VcrU9Y8x+LdM4DLj75VE0MJB8zuF1iUnroEptStw+DQ8EQPMUdfe5k+PkB1uUfDQfWbhstH8LrQ==}
|
||||
|
||||
@ -9405,6 +9414,12 @@ snapshots:
|
||||
|
||||
'@xtuc/long@4.2.2': {}
|
||||
|
||||
'@yornaath/batshit-devtools@1.7.1': {}
|
||||
|
||||
'@yornaath/batshit@0.11.1':
|
||||
dependencies:
|
||||
'@yornaath/batshit-devtools': 1.7.1
|
||||
|
||||
abortcontroller-polyfill@1.7.8: {}
|
||||
|
||||
acorn-import-phases@1.0.4(acorn@8.15.0):
|
||||
|
||||
Reference in New Issue
Block a user