From 8caaffe435f4ca1737cb18c988f9e244f6addfbf Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 6 Feb 2026 14:08:29 +0100 Subject: [PATCH] [Glitch] Move account search into hook Port 7e27ba990e905fd07b1469edeeda8eb0217b3b93 to glitch-soc Signed-off-by: Claire --- .../glitch/features/lists/members.tsx | 65 +++++------------- .../features/lists/use_search_accounts.ts | 67 +++++++++++++++++++ 2 files changed, 85 insertions(+), 47 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/lists/use_search_accounts.ts diff --git a/app/javascript/flavours/glitch/features/lists/members.tsx b/app/javascript/flavours/glitch/features/lists/members.tsx index b874023052..fb62a48520 100644 --- a/app/javascript/flavours/glitch/features/lists/members.tsx +++ b/app/javascript/flavours/glitch/features/lists/members.tsx @@ -1,12 +1,10 @@ -import { useCallback, useState, useEffect, useRef } from 'react'; +import { useCallback, useState, useEffect } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; import { useParams, Link } from 'react-router-dom'; -import { useDebouncedCallback } from 'use-debounce'; - import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react'; import { fetchRelationships } from 'flavours/glitch/actions/accounts'; @@ -14,14 +12,12 @@ import { showAlertForError } from 'flavours/glitch/actions/alerts'; import { importFetchedAccounts } from 'flavours/glitch/actions/importer'; import { fetchList } from 'flavours/glitch/actions/lists'; import { openModal } from 'flavours/glitch/actions/modal'; -import { apiRequest } from 'flavours/glitch/api'; import { apiFollowAccount } from 'flavours/glitch/api/accounts'; import { apiGetAccounts, apiAddAccountToList, apiRemoveAccountFromList, } from 'flavours/glitch/api/lists'; -import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts'; import { Avatar } from 'flavours/glitch/components/avatar'; import { Button } from 'flavours/glitch/components/button'; import { Column } from 'flavours/glitch/components/column'; @@ -35,6 +31,8 @@ import { VerifiedBadge } from 'flavours/glitch/components/verified_badge'; import { me } from 'flavours/glitch/initial_state'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; +import { useSearchAccounts } from './use_search_accounts'; + export const messages = defineMessages({ manageMembers: { id: 'column.list_members', @@ -163,10 +161,23 @@ const ListMembers: React.FC<{ const [searching, setSearching] = useState(false); const [accountIds, setAccountIds] = useState([]); - const [searchAccountIds, setSearchAccountIds] = useState([]); const [loading, setLoading] = useState(!!id); const [mode, setMode] = useState('remove'); + const { + accountIds: searchAccountIds = [], + isLoading: loadingSearchResults, + searchAccounts: handleSearch, + } = useSearchAccounts({ + onSettled: (value) => { + if (value.trim().length === 0) { + setSearching(false); + } else { + setSearching(true); + } + }, + }); + useEffect(() => { if (id) { dispatch(fetchList(id)); @@ -206,46 +217,6 @@ const ListMembers: React.FC<{ [accountIds, setAccountIds], ); - const searchRequestRef = useRef(null); - - const handleSearch = useDebouncedCallback( - (value: string) => { - if (searchRequestRef.current) { - searchRequestRef.current.abort(); - } - - if (value.trim().length === 0) { - setSearching(false); - return; - } - - setLoading(true); - - searchRequestRef.current = new AbortController(); - - void apiRequest('GET', 'v1/accounts/search', { - signal: searchRequestRef.current.signal, - params: { - q: value, - resolve: true, - }, - }) - .then((data) => { - dispatch(importFetchedAccounts(data)); - setSearchAccountIds(data.map((a) => a.id)); - setLoading(false); - setSearching(true); - return ''; - }) - .catch(() => { - setSearching(true); - setLoading(false); - }); - }, - 500, - { leading: true, trailing: true }, - ); - let displayedAccountIds: string[]; if (mode === 'add' && searching) { @@ -279,7 +250,7 @@ const ListMembers: React.FC<{ scrollKey='list_members' trackScroll={!multiColumn} bindToDocument={!multiColumn} - isLoading={loading} + isLoading={loading || loadingSearchResults} showLoading={loading && displayedAccountIds.length === 0} hasMore={false} footer={ diff --git a/app/javascript/flavours/glitch/features/lists/use_search_accounts.ts b/app/javascript/flavours/glitch/features/lists/use_search_accounts.ts new file mode 100644 index 0000000000..1155ff6350 --- /dev/null +++ b/app/javascript/flavours/glitch/features/lists/use_search_accounts.ts @@ -0,0 +1,67 @@ +import { useRef, useState } from 'react'; + +import { useDebouncedCallback } from 'use-debounce'; + +import { importFetchedAccounts } from 'flavours/glitch/actions/importer'; +import { apiRequest } from 'flavours/glitch/api'; +import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts'; +import { useAppDispatch } from 'flavours/glitch/store'; + +export function useSearchAccounts({ + onSettled, +}: { + onSettled?: (value: string) => void; +} = {}) { + const dispatch = useAppDispatch(); + + const [accountIds, setAccountIds] = useState(); + const [loadingState, setLoadingState] = useState< + 'idle' | 'loading' | 'error' + >('idle'); + + const searchRequestRef = useRef(null); + + const searchAccounts = useDebouncedCallback( + (value: string) => { + if (searchRequestRef.current) { + searchRequestRef.current.abort(); + } + + if (value.trim().length === 0) { + onSettled?.(''); + return; + } + + setLoadingState('loading'); + + searchRequestRef.current = new AbortController(); + + void apiRequest('GET', 'v1/accounts/search', { + signal: searchRequestRef.current.signal, + params: { + q: value, + resolve: true, + }, + }) + .then((data) => { + dispatch(importFetchedAccounts(data)); + setAccountIds(data.map((a) => a.id)); + setLoadingState('idle'); + onSettled?.(value); + }) + .catch(() => { + setLoadingState('error'); + onSettled?.(value); + }); + }, + 500, + { leading: true, trailing: true }, + ); + + return { + searchAccounts, + accountIds, + isLoading: loadingState === 'loading', + isError: loadingState === 'error', + }; +}