-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
Copy pathindex.ts
274 lines (239 loc) · 6.99 KB
/
index.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
import React, { useEffect } from "react";
import get from "lodash/get";
import has from "lodash/has";
import {
useForm as useHookForm,
type UseFormProps as UseHookFormProps,
type UseFormReturn,
type FieldValues,
type UseFormHandleSubmit,
type Path,
} from "react-hook-form";
import {
type BaseRecord,
type HttpError,
useForm as useFormCore,
useWarnAboutChange,
type UseFormProps as UseFormCoreProps,
type UseFormReturnType as UseFormReturnTypeCore,
useTranslate,
useRefineContext,
flattenObjectKeys,
} from "@refinedev/core";
export type UseFormReturnType<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables extends FieldValues = FieldValues,
TContext extends object = {},
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = UseFormReturn<TVariables, TContext> & {
refineCore: UseFormReturnTypeCore<
TQueryFnData,
TError,
TVariables,
TData,
TResponse,
TResponseError
>;
saveButtonProps: {
disabled: boolean;
onClick: (e: React.BaseSyntheticEvent) => void;
};
};
export type UseFormProps<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables extends FieldValues = FieldValues,
TContext extends object = {},
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = {
/**
* Configuration object for the core of the [useForm](/docs/api-reference/core/hooks/useForm/)
* @type [`UseFormCoreProps<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError>`](/docs/api-reference/core/hooks/useForm/#properties)
*/
refineCoreProps?: UseFormCoreProps<
TQueryFnData,
TError,
TVariables,
TData,
TResponse,
TResponseError
>;
/**
* When you have unsaved changes and try to leave the current page, **refine** shows a confirmation modal box.
* @default `false*`
*/
warnWhenUnsavedChanges?: boolean;
/**
* Disables server-side validation
* @default false
* @see {@link https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/}
*/
disableServerSideValidation?: boolean;
} & UseHookFormProps<TVariables, TContext>;
export const useForm = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables extends FieldValues = FieldValues,
TContext extends object = {},
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
>({
refineCoreProps,
warnWhenUnsavedChanges: warnWhenUnsavedChangesProp,
disableServerSideValidation: disableServerSideValidationProp = false,
...rest
}: UseFormProps<
TQueryFnData,
TError,
TVariables,
TContext,
TData,
TResponse,
TResponseError
> = {}): UseFormReturnType<
TQueryFnData,
TError,
TVariables,
TContext,
TData,
TResponse,
TResponseError
> => {
const { options } = useRefineContext();
const disableServerSideValidation =
options?.disableServerSideValidation || disableServerSideValidationProp;
const translate = useTranslate();
const { warnWhenUnsavedChanges: warnWhenUnsavedChangesRefine, setWarnWhen } =
useWarnAboutChange();
const warnWhenUnsavedChanges =
warnWhenUnsavedChangesProp ?? warnWhenUnsavedChangesRefine;
const useHookFormResult = useHookForm<TVariables, TContext>({
...rest,
});
const {
watch,
setValue,
getValues,
formState,
handleSubmit: handleSubmitReactHookForm,
setError,
} = useHookFormResult;
const useFormCoreResult = useFormCore<
TQueryFnData,
TError,
TVariables,
TData,
TResponse,
TResponseError
>({
...refineCoreProps,
onMutationError: (error, _variables, _context) => {
if (disableServerSideValidation) {
refineCoreProps?.onMutationError?.(error, _variables, _context);
return;
}
const errors = error?.errors;
for (const key in errors) {
// when the key is not registered in the form, react-hook-form not working
const isKeyInVariables = Object.keys(
flattenObjectKeys(_variables),
).includes(key);
if (!isKeyInVariables) {
continue;
}
const fieldError = errors[key];
let newError = "";
if (Array.isArray(fieldError)) {
newError = fieldError.join(" ");
}
if (typeof fieldError === "string") {
newError = fieldError;
}
if (typeof fieldError === "boolean" && fieldError) {
newError = "Field is not valid.";
}
if (typeof fieldError === "object" && "key" in fieldError) {
const translatedMessage = translate(
fieldError.key,
fieldError.message,
);
newError = translatedMessage;
}
setError(key as Path<TVariables>, {
message: newError,
});
}
refineCoreProps?.onMutationError?.(error, _variables, _context);
},
});
const { query, onFinish, formLoading, onFinishAutoSave } = useFormCoreResult;
useEffect(() => {
const data = query?.data?.data;
if (!data) return;
/**
* get registered fields from react-hook-form
*/
const registeredFields = Object.keys(flattenObjectKeys(getValues()));
/**
* set values from query result as default values
*/
registeredFields.forEach((path) => {
const hasValue = has(data, path);
const dataValue = get(data, path);
/**
* set value if the path exists in the query result even if the value is null
*/
if (hasValue) {
setValue(path as Path<TVariables>, dataValue);
}
});
}, [query?.data, setValue, getValues]);
useEffect(() => {
const subscription = watch((values: any, { type }: { type?: any }) => {
if (type === "change") {
onValuesChange(values);
}
});
return () => subscription.unsubscribe();
}, [watch]);
useEffect(() => {
setWarnWhen(formState.isDirty && warnWhenUnsavedChanges);
}, [formState.isDirty, warnWhenUnsavedChanges]);
const onValuesChange = (changeValues: TVariables) => {
if (refineCoreProps?.autoSave?.enabled) {
setWarnWhen(false);
const onFinishProps =
refineCoreProps.autoSave?.onFinish ?? ((values: TVariables) => values);
return onFinishAutoSave(onFinishProps(changeValues)).catch(
(error) => error,
);
}
return changeValues;
};
const handleSubmit: UseFormHandleSubmit<TVariables> =
(onValid, onInvalid) => async (e) => {
setWarnWhen(false);
return handleSubmitReactHookForm(onValid, onInvalid)(e);
};
const saveButtonProps = {
disabled: formLoading,
onClick: (e: React.BaseSyntheticEvent) => {
handleSubmit(
(v) => onFinish(v).catch(() => {}),
() => false,
)(e);
},
};
return {
...useHookFormResult,
handleSubmit,
refineCore: useFormCoreResult,
saveButtonProps,
};
};