export function useGenericSearchOptions<TItem, TPage extends PaginatedData<TItem>>({
infiniteQueryOptions,
idsQueryOptionsFn,
mapToOptions,
initialSelected,
initialSelectedIds = [],
pageSize = 10,
}: UseGenericSearchOptionsProps<TItem, TPage>) {
const [searchValue, setSearchValue] = useState('');
const [selectedItems, setSelectedItems] = useState<TItem[]>(
Array.isArray(initialSelected) ? initialSelected : initialSelected ? [initialSelected] : [],
);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(
infiniteQueryOptions({ search: searchValue, pageSize }),
);
// Flatten pages into items
const items: TItem[] = useMemo(() => data?.pages.flatMap((page) => page.data) ?? [], [data]);
const fetchedOptions = useMemo(() => items.map(mapToOptions), [items, mapToOptions]);
// Build a lookup map for deduplication
const optionsMap = useMemo(() => new Map(fetchedOptions.map((opt) => [opt.value, opt])), [fetchedOptions]);
// Fetch missing preselected IDs
const missingIds = initialSelectedIds.filter((id) => !optionsMap.has(id));
const missingSelected = useQuery(idsQueryOptionsFn({ ids: missingIds }));
const missingSelectedOptions = useMemo(
() => (missingSelected.data?.items ?? []).map(mapToOptions),
[missingSelected.data, mapToOptions],
);
// Merge: fetched + selected + missing
const mergedOptions = useMemo(() => {
const selectedOptions = selectedItems.map(mapToOptions);
const missingOptions = selectedOptions.filter((opt) => !optionsMap.has(opt.value));
return [...fetchedOptions, ...missingOptions, ...missingSelectedOptions];
}, [fetchedOptions, selectedItems, missingSelectedOptions, optionsMap, mapToOptions]);
return {
options: mergedOptions,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
searchValue,
setSearchValue,
selectedItems,
setSelectedItems,
};
}