Reduce account overfetching when displaying collection lists (#39220)

This commit is contained in:
diondiondion 2026-05-29 17:27:57 +02:00 committed by GitHub
parent 8e15e49e87
commit 0caf334891
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 48 additions and 5 deletions

View File

@ -3,6 +3,7 @@ import { createAction } from '@reduxjs/toolkit';
import { import {
apiRemoveAccountFromFollowers, apiRemoveAccountFromFollowers,
apiGetEndorsedAccounts, apiGetEndorsedAccounts,
apiGetAccounts,
} from 'mastodon/api/accounts'; } from 'mastodon/api/accounts';
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
@ -113,3 +114,12 @@ export const fetchEndorsedAccounts = createDataLoadingThunk(
return data; return data;
}, },
); );
export const fetchAccounts = createDataLoadingThunk(
'accounts/multi_accounts',
({ accountIds }: { accountIds: string[] }) => apiGetAccounts(accountIds),
(data, { dispatch }) => {
dispatch(importFetchedAccounts(data));
return data;
},
);

View File

@ -19,6 +19,11 @@ import type {
ApiProfileUpdateParams, ApiProfileUpdateParams,
} from '../api_types/profile'; } from '../api_types/profile';
export const apiGetAccounts = (ids: string[]) =>
apiRequestGet<ApiAccountJSON[]>('v1/accounts', {
id: ids,
});
export const apiSubmitAccountNote = (id: string, value: string) => export const apiSubmitAccountNote = (id: string, value: string) =>
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, { apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
comment: value, comment: value,

View File

@ -15,7 +15,7 @@ export const apiUpdate = (list: Partial<ApiListJSON>) =>
export const apiGetLists = () => apiRequestGet<ApiListJSON[]>('v1/lists'); export const apiGetLists = () => apiRequestGet<ApiListJSON[]>('v1/lists');
export const apiGetAccounts = (listId: string) => export const apiGetListAccounts = (listId: string) =>
apiRequestGet<ApiAccountJSON[]>(`v1/lists/${listId}/accounts`, { apiRequestGet<ApiAccountJSON[]>(`v1/lists/${listId}/accounts`, {
limit: 0, limit: 0,
}); });

View File

@ -15,7 +15,7 @@ import { fetchList } from 'mastodon/actions/lists';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { apiFollowAccount } from 'mastodon/api/accounts'; import { apiFollowAccount } from 'mastodon/api/accounts';
import { import {
apiGetAccounts, apiGetListAccounts,
apiAddAccountToList, apiAddAccountToList,
apiRemoveAccountFromList, apiRemoveAccountFromList,
} from 'mastodon/api/lists'; } from 'mastodon/api/lists';
@ -184,7 +184,7 @@ const ListMembers: React.FC<{
if (id) { if (id) {
dispatch(fetchList(id)); dispatch(fetchList(id));
void apiGetAccounts(id) void apiGetListAccounts(id)
.then((data) => { .then((data) => {
dispatch(importFetchedAccounts(data)); dispatch(importFetchedAccounts(data));
setAccountIds(data.map((a) => a.id)); setAccountIds(data.map((a) => a.id));

View File

@ -12,7 +12,7 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import { fetchList } from 'mastodon/actions/lists'; import { fetchList } from 'mastodon/actions/lists';
import { createList, updateList } from 'mastodon/actions/lists_typed'; import { createList, updateList } from 'mastodon/actions/lists_typed';
import { apiGetAccounts } from 'mastodon/api/lists'; import { apiGetListAccounts } from 'mastodon/api/lists';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import type { RepliesPolicyType } from 'mastodon/api_types/lists'; import type { RepliesPolicyType } from 'mastodon/api_types/lists';
import { Avatar } from 'mastodon/components/avatar'; import { Avatar } from 'mastodon/components/avatar';
@ -44,7 +44,7 @@ const MembersLink: React.FC<{
const [avatarAccounts, setAvatarAccounts] = useState<ApiAccountJSON[]>([]); const [avatarAccounts, setAvatarAccounts] = useState<ApiAccountJSON[]>([]);
useEffect(() => { useEffect(() => {
void apiGetAccounts(id) void apiGetListAccounts(id)
.then((data) => { .then((data) => {
setAvatarCount(data.length); setAvatarCount(data.length);
setAvatarAccounts(data.slice(0, 3)); setAvatarAccounts(data.slice(0, 3));

View File

@ -1,6 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import { fetchAccounts } from '@/mastodon/actions/accounts_typed';
import { importFetchedAccounts } from '@/mastodon/actions/importer'; import { importFetchedAccounts } from '@/mastodon/actions/importer';
import { import {
apiCreateCollection, apiCreateCollection,
@ -20,6 +21,7 @@ import type {
CollectionAccountItem, CollectionAccountItem,
} from '@/mastodon/api_types/collections'; } from '@/mastodon/api_types/collections';
import { initialState, me } from '@/mastodon/initial_state'; import { initialState, me } from '@/mastodon/initial_state';
import type { AppDispatch } from '@/mastodon/store';
import { import {
createAppAsyncThunk, createAppAsyncThunk,
createAppSelector, createAppSelector,
@ -322,16 +324,42 @@ const collectionSlice = createSlice({
}, },
}); });
/**
* Prefetch accounts whose avatars will be displayed in the collection list
*/
async function importAccountsForPreviewCard(
collections: ApiCollectionJSON[],
dispatch: AppDispatch,
) {
const previewAccountIds = collections
.flatMap((collection) =>
collection.items.slice(0, 3).map((item) => item.account_id),
)
.filter((id): id is string => !!id);
await dispatch(
fetchAccounts({
accountIds: previewAccountIds,
}),
);
}
export const fetchCollectionsCreatedByAccount = createDataLoadingThunk( export const fetchCollectionsCreatedByAccount = createDataLoadingThunk(
`${collectionSlice.name}/fetchCollectionsCreatedByAccount`, `${collectionSlice.name}/fetchCollectionsCreatedByAccount`,
({ accountId }: { accountId: string }) => ({ accountId }: { accountId: string }) =>
apiGetCollectionsCreatedByAccount(accountId), apiGetCollectionsCreatedByAccount(accountId),
async ({ collections }, { dispatch }) => {
await importAccountsForPreviewCard(collections, dispatch);
},
); );
export const fetchCollectionsFeaturingAccount = createDataLoadingThunk( export const fetchCollectionsFeaturingAccount = createDataLoadingThunk(
`${collectionSlice.name}/fetchCollectionsFeaturingAccount`, `${collectionSlice.name}/fetchCollectionsFeaturingAccount`,
({ accountId }: { accountId: string }) => ({ accountId }: { accountId: string }) =>
apiGetCollectionsFeaturingAccount(accountId), apiGetCollectionsFeaturingAccount(accountId),
async ({ collections }, { dispatch }) => {
await importAccountsForPreviewCard(collections, dispatch);
},
); );
export const fetchCollection = createDataLoadingThunk( export const fetchCollection = createDataLoadingThunk(