UNPKG

ra-core

Version:

Core components of react-admin, a frontend Framework for building admin applications on top of REST services, using ES6, React

151 lines (140 loc) 5 kB
import { useCallback, useRef } from 'react'; import merge from 'lodash/merge'; import set from 'lodash/set'; import { useResourceContext } from '../../core'; import { useDataProvider } from '../../dataProvider'; import { useTranslate, useTranslateLabel } from '../../i18n'; import { asyncDebounce } from '../../util'; import { useRecordContext } from '../../controller'; import { InputProps } from '../useInput'; import { isEmpty } from './validate'; /** * A hook that returns a validation function checking for a record field uniqueness * by calling the dataProvider getList function with a filter. * * @example // Passing options at declaration time * const UserCreateForm = () => { * const unique = useUnique({ message: 'Username is already used'}); * return ( * <SimpleForm> * <TextInput source="username" validate={unique()} /> * </SimpleForm> * ); * } * * @example // Passing options at call time * const UserCreateForm = () => { * const unique = useUnique(); * return ( * <SimpleForm> * <TextInput source="username" validate={unique({ message: 'Username is already used'})} /> * </SimpleForm> * ); * } * * @example // With additional filters * const UserCreateForm = () => { * const unique = useUnique(); * return ( * <SimpleForm> * <ReferenceInput source="organization_id" reference="organizations" /> * <FormDataConsumer> * {({ formData }) => ( * <TextInput * source="username" * validate={unique({ filter: { organization_id: formData.organization_id })} * /> * )} * </FormDataConsumer> * </SimpleForm> * ); * } */ export const useUnique = (options?: UseUniqueOptions) => { const dataProvider = useDataProvider(); const translateLabel = useTranslateLabel(); const resource = useResourceContext(options); if (!resource) { throw new Error('useUnique: missing resource prop or context'); } const translate = useTranslate(); const record = useRecordContext(); const debouncedGetList = useRef( // The initial value is here to set the correct type on useRef asyncDebounce( dataProvider.getList, options?.debounce ?? DEFAULT_DEBOUNCE ) ); const validateUnique = useCallback( (callTimeOptions?: UseUniqueOptions) => { const { message, filter, debounce: interval, } = merge<UseUniqueOptions, any, any>( { debounce: DEFAULT_DEBOUNCE, filter: {}, message: 'ra.validation.unique', }, options, callTimeOptions ); debouncedGetList.current = asyncDebounce( dataProvider.getList, interval ); return async (value: any, allValues: any, props: InputProps) => { if (isEmpty(value)) { return undefined; } try { const finalFilter = set( merge({}, filter), props.source, value ); const { data, total } = await debouncedGetList.current( resource, { filter: finalFilter, pagination: { page: 1, perPage: 1 }, sort: { field: 'id', order: 'ASC' }, } ); if ( typeof total !== 'undefined' && total > 0 && !data.some(r => r.id === record?.id) ) { return { message, args: { source: props.source, value, field: translateLabel({ label: props.label, source: props.source, resource, }), }, }; } } catch (error) { return translate('ra.notification.http_error'); } return undefined; }; }, [dataProvider, options, record, resource, translate, translateLabel] ); return validateUnique; }; const DEFAULT_DEBOUNCE = 1000; export type UseUniqueOptions = { debounce?: number; resource?: string; message?: string; filter?: Record<string, any>; };