import React from "react"; import uniqBy from "lodash/uniqBy"; import { SelectProps } from "antd/lib/select"; import { QueryObserverResult, UseQueryOptions } from "react-query"; import debounce from "lodash/debounce"; import { useList, useMany } from "@hooks"; import { CrudSorting, Option, BaseRecord, GetManyResponse, GetListResponse, CrudFilters, SuccessErrorNotification, HttpError, MetaDataQuery, LiveModeProps, } from "../../../interfaces"; export type UseSelectProps = { resource: string; optionLabel?: string; optionValue?: string; sort?: CrudSorting; filters?: CrudFilters; defaultValue?: string | string[]; debounce?: number; queryOptions?: UseQueryOptions, TError>; fetchSize?: number; defaultValueQueryOptions?: UseQueryOptions, TError>; onSearch?: (value: string) => CrudFilters; metaData?: MetaDataQuery; } & SuccessErrorNotification & LiveModeProps; export type UseSelectReturnType = { selectProps: SelectProps<{ value: string; label: string }>; queryResult: QueryObserverResult>; defaultValueQueryResult: QueryObserverResult>; }; /** * `useSelect` hook allows you to manage an Ant Design {@link https://ant.design/components/select/ Select} component when records in a resource needs to be used as select options. * * @see {@link https://refine.dev/docs/api-references/hooks/field/useSelect} for more details. * * @typeParam TData - Result data of the query extends {@link https://refine.dev/docs/api-references/interfaceReferences#baserecord `BaseRecord`} * */ export const useSelect = < TData extends BaseRecord = BaseRecord, TError extends HttpError = HttpError, >( props: UseSelectProps, ): UseSelectReturnType => { const [search, setSearch] = React.useState([]); const [options, setOptions] = React.useState([]); const [selectedOptions, setSelectedOptions] = React.useState([]); let { defaultValue = [] } = props; const { resource, sort, filters = [], optionLabel = "title", optionValue = "id", debounce: debounceValue = 300, successNotification, errorNotification, defaultValueQueryOptions, queryOptions, fetchSize, liveMode, onLiveEvent, onSearch: onSearchFromProp, liveParams, metaData, } = props; if (!Array.isArray(defaultValue)) { defaultValue = [defaultValue]; } const defaultValueQueryOnSuccess = (data: GetManyResponse) => { setSelectedOptions( data.data.map((item) => ({ label: item[optionLabel], value: item[optionValue], })), ); }; const defaultValueQueryResult = useMany({ resource, ids: defaultValue, queryOptions: { enabled: defaultValue.length > 0, ...defaultValueQueryOptions, onSuccess: (data) => { defaultValueQueryOnSuccess(data); defaultValueQueryOptions?.onSuccess?.(data); }, }, metaData, liveMode: "off", }); const defaultQueryOnSuccess = (data: GetListResponse) => { setOptions( data.data.map((item) => ({ label: item[optionLabel], value: item[optionValue], })), ); }; const queryResult = useList({ resource, config: { sort, filters: filters.concat(search), pagination: fetchSize ? { pageSize: fetchSize, } : undefined, }, queryOptions: { ...queryOptions, onSuccess: (data) => { defaultQueryOnSuccess(data); queryOptions?.onSuccess?.(data); }, }, successNotification, errorNotification, metaData, liveMode, liveParams, onLiveEvent, }); const { refetch: refetchList } = queryResult; React.useEffect(() => { if (search) { refetchList(); } }, [search]); const onSearch = (value: string) => { if (!value) { setSearch([]); return; } if (onSearchFromProp) { setSearch(onSearchFromProp(value)); } else { setSearch([ { field: optionLabel, operator: "contains", value, }, ]); } }; return { selectProps: { options: uniqBy([...options, ...selectedOptions], "value"), onSearch: debounce(onSearch, debounceValue), loading: defaultValueQueryResult.isFetching, showSearch: true, filterOption: false, }, queryResult, defaultValueQueryResult, }; };