Mastodon/app/javascript/mastodon/hooks/useSearchAccounts.ts

156 lines
3.9 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
import { fetchRelationships } from 'mastodon/actions/accounts';
import { importFetchedAccounts } from 'mastodon/actions/importer';
import { apiRequest } from 'mastodon/api';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import { useAppDispatch } from 'mastodon/store';
import { useCurrentAccountId } from './useAccountId';
export function useSearchAccounts({
onSettled,
filterResults,
resetOnInputClear = true,
withRelationships = false,
withDefaultFollows = false,
}: {
onSettled?: (value: string) => void;
filterResults?: (account: ApiAccountJSON) => boolean;
resetOnInputClear?: boolean;
withRelationships?: boolean;
withDefaultFollows?: boolean;
} = {}) {
const dispatch = useAppDispatch();
const [accounts, setAccounts] = useState<ApiAccountJSON[]>([]);
const [loadingState, setLoadingState] = useState<
'idle' | 'loading' | 'error'
>('idle');
const searchRequestRef = useRef<AbortController | null>(null);
const searchAccounts = useDebouncedCallback(
async (value: string) => {
if (searchRequestRef.current) {
searchRequestRef.current.abort();
}
if (value.trim().length === 0) {
onSettled?.('');
if (resetOnInputClear) {
setAccounts([]);
}
return;
}
setLoadingState('loading');
searchRequestRef.current = new AbortController();
try {
const accounts = await apiRequest<ApiAccountJSON[]>(
'GET',
'v1/accounts/search',
{
signal: searchRequestRef.current.signal,
params: {
q: value,
resolve: true,
},
},
);
const accountIds = accounts.map((a) => a.id);
dispatch(importFetchedAccounts(accounts));
if (withRelationships) {
dispatch(fetchRelationships(accountIds));
}
setAccounts(accounts);
setLoadingState('idle');
onSettled?.(value);
} catch {
setLoadingState('error');
onSettled?.(value);
}
},
500,
{ leading: true, trailing: true },
);
const startSearch = useCallback(
(value: string) => {
void searchAccounts(value);
},
[searchAccounts],
);
const resetAccounts = useCallback(() => {
setAccounts([]);
}, []);
const currentUserId = useCurrentAccountId();
const [defaultAccounts, setDefaultAccounts] = useState<
ApiAccountJSON[] | null
>(null);
useEffect(() => {
if (
!currentUserId ||
loadingState !== 'idle' ||
defaultAccounts !== null ||
!withDefaultFollows
) {
return;
}
async function doRequest() {
setLoadingState('loading');
try {
const accounts = await apiRequest<ApiAccountJSON[]>(
'GET',
`v1/accounts/${currentUserId}/following`,
{ params: { limit: 40 } },
);
const accountIds = accounts.map((a) => a.id);
dispatch(importFetchedAccounts(accounts));
if (withRelationships) {
dispatch(fetchRelationships(accountIds));
}
setDefaultAccounts(accounts);
setLoadingState('idle');
} catch {
setLoadingState('error');
}
}
void doRequest();
}, [
currentUserId,
accounts,
dispatch,
filterResults,
loadingState,
withRelationships,
defaultAccounts,
withDefaultFollows,
]);
const accountsToReturn =
accounts.length === 0 && withDefaultFollows
? (defaultAccounts ?? [])
: accounts;
const filteredAccounts = filterResults
? accountsToReturn.filter(filterResults)
: accountsToReturn;
return {
searchAccounts: startSearch,
resetAccounts,
accounts: filteredAccounts,
isLoading: loadingState === 'loading',
isError: loadingState === 'error',
};
}