Skip to content

Commit 54a33fa

Browse files
committed
refactor: separate errorAtPath into its own hook (#16393)
1 parent febdf4f commit 54a33fa

File tree

6 files changed

+37
-40
lines changed

6 files changed

+37
-40
lines changed

airbyte-webapp/src/components/forms/SchemaForm/Controls/AdditionalPropertiesControl.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { SchemaFormControl } from "./SchemaFormControl";
1515
import { BaseControlComponentProps } from "./types";
1616
import { useToggleConfig } from "./useToggleConfig";
1717
import { useSchemaForm } from "../SchemaForm";
18+
import { useErrorAtPath } from "../useErrorAtPath";
1819

1920
// Pattern to detect internal keys
2021
const INTERNAL_KEY_PATTERN = /^_key\d+$/;
@@ -94,9 +95,10 @@ export const AdditionalPropertiesControl = ({
9495
hideBorder = false,
9596
}: BaseControlComponentProps) => {
9697
const { formatMessage } = useIntl();
97-
const { errorAtPath, extractDefaultValuesFromSchema } = useSchemaForm();
98+
const { extractDefaultValuesFromSchema } = useSchemaForm();
9899
const { setValue } = useFormContext();
99100
const toggleConfig = useToggleConfig(baseProps.name, additionalPropertiesSchema);
101+
const error = useErrorAtPath(baseProps.name);
100102

101103
// Get all current key-value pairs
102104
const rawFormValue = useWatch({ name: baseProps.name });
@@ -195,7 +197,7 @@ export const AdditionalPropertiesControl = ({
195197
title={baseProps.label}
196198
tooltip={baseProps.labelTooltip}
197199
path={baseProps.name}
198-
error={errorAtPath(baseProps.name)}
200+
error={error}
199201
toggleConfig={baseProps.optional ? toggleConfig : undefined}
200202
header={baseProps.header}
201203
>

airbyte-webapp/src/components/forms/SchemaForm/Controls/ArrayOfObjectsControl.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ControlGroup } from "./ControlGroup";
1313
import { SchemaFormControl } from "./SchemaFormControl";
1414
import { BaseControlComponentProps } from "./types";
1515
import { useSchemaForm } from "../SchemaForm";
16+
import { useErrorAtPath } from "../useErrorAtPath";
1617
import { AirbyteJsonSchema } from "../utils";
1718

1819
export const ArrayOfObjectsControl = ({
@@ -21,10 +22,10 @@ export const ArrayOfObjectsControl = ({
2122
skipRenderedPathRegistration = false,
2223
overrideByPath = {},
2324
}: BaseControlComponentProps) => {
24-
const { errorAtPath, extractDefaultValuesFromSchema } = useSchemaForm();
25+
const { extractDefaultValuesFromSchema } = useSchemaForm();
2526
const { fields: items, append, remove } = useFieldArray({ name: baseProps.name });
2627
// react-hook-form adds "root" to the path of errors on the array of objects field
27-
const error = errorAtPath(`${baseProps.name}.root`);
28+
const error = useErrorAtPath(`${baseProps.name}.root`);
2829

2930
if (!fieldSchema.items) {
3031
throw new Error("items is required on array of object fields");

airbyte-webapp/src/components/forms/SchemaForm/Controls/MultiOptionControl.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { SchemaFormControl } from "./SchemaFormControl";
1313
import { BaseControlComponentProps, OverrideByPath, BaseControlProps } from "./types";
1414
import { useToggleConfig } from "./useToggleConfig";
1515
import { useSchemaForm } from "../SchemaForm";
16+
import { useErrorAtPath } from "../useErrorAtPath";
1617
import { AirbyteJsonSchema, resolveTopLevelRef } from "../utils";
1718

1819
export const MultiOptionControl = ({
@@ -28,12 +29,11 @@ export const MultiOptionControl = ({
2829
const {
2930
schema: rootSchema,
3031
getSelectedOptionSchema,
31-
errorAtPath,
3232
extractDefaultValuesFromSchema,
3333
verifyArrayItems,
3434
} = useSchemaForm();
3535
const toggleConfig = useToggleConfig(baseProps.name, fieldSchema);
36-
const error = errorAtPath(baseProps.name);
36+
const error = useErrorAtPath(baseProps.name);
3737
const optionSchemas = fieldSchema.oneOf ?? fieldSchema.anyOf;
3838
const options = useMemo(
3939
() =>

airbyte-webapp/src/components/forms/SchemaForm/Controls/ObjectControl.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ import styles from "./ObjectControl.module.scss";
1313
import { SchemaFormControl } from "./SchemaFormControl";
1414
import { BaseControlComponentProps, BaseControlProps } from "./types";
1515
import { useToggleConfig } from "./useToggleConfig";
16-
import { useSchemaForm } from "../SchemaForm";
16+
import { useErrorAtPath } from "../useErrorAtPath";
1717
import { AirbyteJsonSchema, getDeclarativeSchemaTypeValue, displayName } from "../utils";
18+
1819
export const ObjectControl = ({
1920
fieldSchema,
2021
baseProps,
@@ -23,10 +24,10 @@ export const ObjectControl = ({
2324
hideBorder = false,
2425
nonAdvancedFields,
2526
}: BaseControlComponentProps) => {
26-
const { errorAtPath } = useSchemaForm();
2727
const { errors } = useFormState();
2828
const value = useWatch({ name: baseProps.name });
2929
const toggleConfig = useToggleConfig(baseProps.name, fieldSchema);
30+
const error = useErrorAtPath(baseProps.name);
3031

3132
if (!fieldSchema.properties) {
3233
if (!fieldSchema.additionalProperties) {
@@ -135,7 +136,7 @@ export const ObjectControl = ({
135136
title={baseProps.label}
136137
tooltip={baseProps.labelTooltip}
137138
path={baseProps.name}
138-
error={errorAtPath(baseProps.name)}
139+
error={error}
139140
toggleConfig={baseProps.optional ? toggleConfig : undefined}
140141
header={baseProps.header}
141142
>
@@ -152,7 +153,7 @@ const JsonEditor = ({
152153
toggleConfig: ReturnType<typeof useToggleConfig>;
153154
}) => {
154155
const { formatMessage } = useIntl();
155-
const { errorAtPath } = useSchemaForm();
156+
const error = useErrorAtPath(baseProps.name);
156157
const value = useWatch({ name: baseProps.name });
157158
const { setValue } = useFormContext();
158159
const [textValue, setTextValue] = useState(JSON.stringify(value, null, 2));
@@ -169,7 +170,7 @@ const JsonEditor = ({
169170
title={displayName(baseProps.name, baseProps.label)}
170171
tooltip={baseProps.labelTooltip}
171172
optional={baseProps.optional}
172-
error={errorAtPath(baseProps.name)}
173+
error={error}
173174
toggleConfig={baseProps.optional ? toggleConfig : undefined}
174175
footer={!textValue ? formatMessage({ id: "form.enterValidJson" }) : undefined}
175176
>

airbyte-webapp/src/components/forms/SchemaForm/SchemaForm.tsx

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
import { ExtendedJSONSchema } from "json-schema-to-ts";
22
import isBoolean from "lodash/isBoolean";
33
import { createContext, useCallback, useContext, useMemo, useRef } from "react";
4-
import {
5-
DefaultValues,
6-
FieldError,
7-
FieldValues,
8-
FormProvider,
9-
get,
10-
useForm,
11-
useFormContext,
12-
useFormState,
13-
} from "react-hook-form";
4+
import { DefaultValues, FieldValues, FormProvider, get, useForm, useFormContext } from "react-hook-form";
145

156
import { DynamicValidator } from "./DynamicValidator";
167
import { RefsHandlerProvider } from "./RefsHandler";
@@ -140,7 +131,6 @@ interface SchemaFormContextValue {
140131
schema: AirbyteJsonSchema;
141132
onlyShowErrorIfTouched?: boolean;
142133
nestedUnderPath?: string;
143-
errorAtPath: (path: string) => FieldError | undefined;
144134
extractDefaultValuesFromSchema: <T extends FieldValues>(fieldSchema: AirbyteJsonSchema) => DefaultValues<T>;
145135
verifyArrayItems: (
146136
items:
@@ -178,27 +168,10 @@ const SchemaFormProvider: React.FC<React.PropsWithChildren<SchemaFormProviderPro
178168
onlyShowErrorIfTouched,
179169
nestedUnderPath,
180170
}) => {
181-
const { errors, touchedFields } = useFormState();
182171
const { getValues } = useFormContext();
183172
// Use a ref instead of state for rendered paths to prevent temporarily rendering fields twice
184173
const renderedPathsRef = useRef<Set<string>>(new Set<string>());
185174

186-
// Setup validation functions
187-
const errorAtPath = (path: string): FieldError | undefined => {
188-
const error: FieldError | undefined = get(errors, path);
189-
const touched = get(touchedFields, path);
190-
if ((onlyShowErrorIfTouched && !touched) || !error?.message) {
191-
return undefined;
192-
}
193-
return {
194-
type: error.type,
195-
message: error.message,
196-
ref: error.ref,
197-
types: error.types,
198-
root: error.root,
199-
};
200-
};
201-
202175
// Update rendered paths tracking functions to use ref
203176
const registerRenderedPath = useCallback((path: string) => {
204177
if (path) {
@@ -277,7 +250,6 @@ const SchemaFormProvider: React.FC<React.PropsWithChildren<SchemaFormProviderPro
277250
schema,
278251
onlyShowErrorIfTouched,
279252
nestedUnderPath,
280-
errorAtPath,
281253
extractDefaultValuesFromSchema: extractDefaultValuesFromSchemaCallback,
282254
verifyArrayItems: verifyArrayItemsCallback,
283255
getSelectedOptionSchema: getSelectedOptionSchemaCallback,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { FieldError, get, useFormState } from "react-hook-form";
2+
3+
import { useSchemaForm } from "./SchemaForm";
4+
5+
export const useErrorAtPath = (path: string): FieldError | undefined => {
6+
const { errors, touchedFields } = useFormState();
7+
const { onlyShowErrorIfTouched } = useSchemaForm();
8+
9+
const error: FieldError | undefined = get(errors, path);
10+
const touched = get(touchedFields, path);
11+
if ((onlyShowErrorIfTouched && !touched) || !error?.message) {
12+
return undefined;
13+
}
14+
return {
15+
type: error.type,
16+
message: error.message,
17+
ref: error.ref,
18+
types: error.types,
19+
root: error.root,
20+
};
21+
};

0 commit comments

Comments
 (0)