mattermost-mobile/app/components/server_user_list/index.tsx
Daniel Espino García cff12f7182
Enable exhaustive deps in the repo (#9508)
* Enable exhaustive deps in the repo

* Add tests for the new hooks

* Update claude.md

* Remove pre-commit overwrite
2026-02-18 11:56:16 +01:00

136 lines
4.2 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import UserList from '@components/user_list';
import {General} from '@constants';
import useDidMount from '@hooks/did_mount';
import {useDebounce} from '@hooks/utils';
import {filterProfilesMatchingTerm} from '@utils/user';
import type {AvailableScreens} from '@typings/screens/navigation';
import type {SectionListData} from 'react-native';
type Props = {
tutorialWatched: boolean;
handleSelectProfile: (user: UserProfile) => void;
term: string;
selectedIds: Set<string>;
fetchFunction: (page: number) => Promise<UserProfile[]>;
searchFunction: (term: string) => Promise<UserProfile[]>;
createFilter: (exactMatches: UserProfile[], term: string) => ((p: UserProfile) => boolean);
testID: string;
location: AvailableScreens;
customSection?: (profiles: UserProfile[]) => Array<SectionListData<UserProfile>>;
}
export default function ServerUserList({
tutorialWatched,
handleSelectProfile,
term,
selectedIds,
fetchFunction,
searchFunction,
createFilter,
testID,
location,
customSection,
}: Props) {
const searchTimeoutId = useRef<NodeJS.Timeout | null>(null);
const next = useRef(true);
const page = useRef(-1);
const mounted = useRef(false);
const [profiles, setProfiles] = useState<UserProfile[]>([]);
const [searchResults, setSearchResults] = useState<UserProfile[]>([]);
const [loading, setLoading] = useState(false);
const isSearch = Boolean(term);
const loadedProfiles = (users: UserProfile[]) => {
if (mounted.current) {
if (!users.length) {
next.current = false;
}
page.current += 1;
setLoading(false);
setProfiles((current) => {
if (users?.length) {
return [...current, ...users];
}
return current;
});
}
};
const getProfiles = useDebounce(useCallback(() => {
if (next.current && !loading && !term && mounted.current) {
setLoading(true);
fetchFunction(page.current + 1).then(loadedProfiles);
}
}, [loading, term, fetchFunction]), 100);
const searchUsers = useCallback(async (searchTerm: string) => {
setLoading(true);
const data = await searchFunction(searchTerm);
setSearchResults(data);
setLoading(false);
}, [searchFunction]);
useEffect(() => {
if (term) {
if (searchTimeoutId.current) {
clearTimeout(searchTimeoutId.current);
}
searchTimeoutId.current = setTimeout(() => {
searchUsers(term);
}, General.SEARCH_TIMEOUT_MILLISECONDS);
} else {
setSearchResults([]);
}
// We only want to run the search when the term changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [term]);
useDidMount(() => {
mounted.current = true;
getProfiles();
return () => {
mounted.current = false;
};
});
const data = useMemo(() => {
if (isSearch) {
const exactMatches: UserProfile[] = [];
const filterByTerm = createFilter(exactMatches, term);
const profilesToFilter = searchResults.length ? searchResults : profiles;
const results = filterProfilesMatchingTerm(profilesToFilter, term).filter(filterByTerm);
return [...exactMatches, ...results];
}
return profiles;
}, [isSearch, profiles, createFilter, term, searchResults]);
return (
<UserList
handleSelectProfile={handleSelectProfile}
loading={loading}
profiles={data}
selectedIds={selectedIds}
showNoResults={!loading && page.current !== -1}
fetchMore={getProfiles}
term={term}
testID={testID}
tutorialWatched={tutorialWatched}
includeUserMargin={true}
location={location}
customSection={customSection}
/>
);
}