forked from marmelab/react-admin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathuseInput.ts
151 lines (135 loc) · 5.01 KB
/
useInput.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { ReactElement, useEffect } from 'react';
import {
ControllerFieldState,
ControllerRenderProps,
useController,
UseControllerProps,
UseControllerReturn,
UseFormStateReturn,
} from 'react-hook-form';
import get from 'lodash/get';
import { useRecordContext } from '../controller';
import { composeValidators, Validator } from './validate';
import isRequired from './isRequired';
import { useFormGroupContext } from './useFormGroupContext';
import { useGetValidationErrorMessage } from './useGetValidationErrorMessage';
import { useFormGroups } from './useFormGroups';
import { useApplyInputDefaultValues } from './useApplyInputDefaultValues';
import { useEvent } from '../util';
// replace null or undefined values by empty string to avoid controlled/uncontrolled input warning
const defaultFormat = (value: any) => (value == null ? '' : value);
// parse empty string into null as it's more suitable for a majority of backends
const defaultParse = (value: string) => (value === '' ? null : value);
export const useInput = <ValueType = any>(
props: InputProps<ValueType>
): UseInputValue => {
const {
defaultValue,
format = defaultFormat,
id,
isRequired: isRequiredOption,
name,
onBlur: initialOnBlur,
onChange: initialOnChange,
parse = defaultParse,
source,
validate,
...options
} = props;
const finalName = name || source;
const formGroupName = useFormGroupContext();
const formGroups = useFormGroups();
const record = useRecordContext();
const getValidationErrorMessage = useGetValidationErrorMessage();
useEffect(() => {
if (!formGroups || formGroupName == null) {
return;
}
formGroups.registerField(source, formGroupName);
return () => {
formGroups.unregisterField(source, formGroupName);
};
}, [formGroups, formGroupName, source]);
const sanitizedValidate = Array.isArray(validate)
? composeValidators(validate)
: validate;
// Fetch the defaultValue from the record if available or apply the provided defaultValue.
// This ensures dynamically added inputs have their value set correctly (ArrayInput for example).
// We don't do this for the form level defaultValues so that it works as it should in react-hook-form
// (i.e. field level defaultValue override form level defaultValues for this field).
const { field: controllerField, fieldState, formState } = useController({
name: finalName,
defaultValue: get(record, source, defaultValue),
rules: {
validate: async (value, values) => {
if (!sanitizedValidate) return true;
const error = await sanitizedValidate(value, values, props);
if (!error) return true;
return getValidationErrorMessage(error);
},
},
...options,
});
// Because our forms may receive an asynchronously loaded record for instance,
// they may reset their default values which would override the input default value.
// This hook ensures that the input default value is applied when a new record is loaded but has
// no value for the input.
useApplyInputDefaultValues(props);
const onBlur = useEvent((...event: any[]) => {
controllerField.onBlur();
if (initialOnBlur) {
initialOnBlur(...event);
}
});
const onChange = useEvent((...event: any[]) => {
const eventOrValue = (props.type === 'checkbox' &&
event[0]?.target?.value === 'on'
? event[0].target.checked
: event[0]?.target?.value ?? event[0]) as any;
controllerField.onChange(parse ? parse(eventOrValue) : eventOrValue);
if (initialOnChange) {
initialOnChange(...event);
}
});
const field = {
...controllerField,
value: format ? format(controllerField.value) : controllerField.value,
onBlur,
onChange,
};
return {
id: id || source,
field,
fieldState,
formState,
isRequired: isRequiredOption || isRequired(validate),
};
};
export type InputProps<ValueType = any> = Omit<
UseControllerProps,
'name' | 'defaultValue' | 'rules'
> &
Partial<UseControllerReturn> & {
alwaysOn?: any;
defaultValue?: any;
format?: (value: ValueType) => any;
id?: string;
isRequired?: boolean;
label?: string | ReactElement | false;
helperText?: string | ReactElement | false;
name?: string;
onBlur?: (...event: any[]) => void;
onChange?: (...event: any[]) => void;
parse?: (value: any) => ValueType;
type?: string;
resource?: string;
source: string;
validate?: Validator | Validator[];
};
export type UseInputValue = {
id: string;
isRequired: boolean;
field: ControllerRenderProps;
formState: UseFormStateReturn<Record<string, string>>;
fieldState: ControllerFieldState;
};