diff --git a/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx b/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx index 5e6a1892..c0e6c612 100644 --- a/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx +++ b/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx @@ -18,20 +18,20 @@ const SchemaRendererComponent: React.FC = ({ config, errorMessages, mode, - name, + name: headName, schema, }) => { - const serviceFieldName = `DYNAMIC_FORMS_SERVICE_FIELD/${name}`; + const serviceFieldName = `DYNAMIC_FORMS_SERVICE_FIELD.${headName}`; const context: SchemaRendererContextType = React.useMemo( () => ({ config, mode, - name, + headName, schema, serviceFieldName, }), - [config, mode, name, schema, serviceFieldName], + [config, mode, headName, schema, serviceFieldName], ); return ( @@ -39,11 +39,11 @@ const SchemaRendererComponent: React.FC = ({ - + ); }; diff --git a/src/lib/unstable/core/SchemaRendererContext/types.ts b/src/lib/unstable/core/SchemaRendererContext/types.ts index fc8e074c..c5073ee9 100644 --- a/src/lib/unstable/core/SchemaRendererContext/types.ts +++ b/src/lib/unstable/core/SchemaRendererContext/types.ts @@ -4,7 +4,7 @@ import type {JsonSchema, SchemaRendererConfig} from '../types'; export interface SchemaRendererContextType { config: SchemaRendererConfig; mode: SchemaRendererMode; - name: string; + headName: string; schema: JsonSchema; serviceFieldName: string; } diff --git a/src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx b/src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx index 36388315..d96de57d 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx +++ b/src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx @@ -9,7 +9,7 @@ import {getValidate} from './utils'; export interface SchemaRendererProps { config: SchemaRendererConfig; errorMessages?: ErrorMessages; - name: string; + headName: string; mainSchema: JsonSchema; serviceFieldName: string; } @@ -17,7 +17,7 @@ export interface SchemaRendererProps { const SchemaRendererServiceFieldComponent: React.FC = ({ config, errorMessages, - name, + headName, mainSchema, serviceFieldName, }) => { @@ -28,7 +28,7 @@ const SchemaRendererServiceFieldComponent: React.FC = ({ getValidate({ config, errorMessages, - name, + headName, mainSchema, serviceFieldName, setValidationCache, @@ -37,7 +37,7 @@ const SchemaRendererServiceFieldComponent: React.FC = ({ [ config, errorMessages, - name, + headName, mainSchema, serviceFieldName, setValidationCache, diff --git a/src/lib/unstable/core/SchemaRendererServiceField/types.ts b/src/lib/unstable/core/SchemaRendererServiceField/types.ts index 354b422d..23a95fa3 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/types.ts +++ b/src/lib/unstable/core/SchemaRendererServiceField/types.ts @@ -41,7 +41,7 @@ export interface GetAjvErrorMessageParams { export type GetValidateParams = { config: SchemaRendererConfig; errorMessages?: ErrorMessages; - name: string; + headName: string; mainSchema: Schema; serviceFieldName: string; setValidationCache: SetValidationCacheMutator; diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils.ts index 3bbe3224..569398c9 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils.ts +++ b/src/lib/unstable/core/SchemaRendererServiceField/utils.ts @@ -3,13 +3,14 @@ import Ajv from 'ajv'; import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; import isString from 'lodash/isString'; +import mapValues from 'lodash/mapValues'; import omit from 'lodash/omit'; import set from 'lodash/set'; -import {ARRAY_AND_OBJECT_ERRORS, EMPTY_OBJECT, JsonSchemaType} from '../constants'; -import type {ValidationState, ValidationWaiter} from '../mutators'; +import {ARRAY_AND_OBJECT_ERRORS, EMPTY_OBJECT, JsonSchemaType, OBJECT_ERROR} from '../constants'; +import type {ErrorsState, ValidationState, ValidationWaiter} from '../mutators'; import type {FieldValue, JsonSchema, ObjectValue, SyncValidateError, Validator} from '../types'; -import {parseFinalFormPath} from '../utils'; +import {getSchemaByFinalFormPath, parseFinalFormPath} from '../utils'; import type { EntityParametersError, @@ -73,6 +74,10 @@ const parseSchemaPath = (schemaPath: string): string[] => { }; const parseInstancePath = (instancePath: string): string[] => { + if (!instancePath.length) { + return []; + } + return instancePath .slice('/'.length) .split('/') @@ -85,7 +90,11 @@ const getSchemaBySchemaPath = ( ): JsonSchema | undefined => { const pathArr = parseSchemaPath(schemaPath); - return pathArr.length ? get(mainSchema, pathArr) : mainSchema; + if (!pathArr.length) { + return mainSchema; + } + + return get(mainSchema, pathArr); }; const getSchemaByInstancePath = ( @@ -148,7 +157,7 @@ const getAjvErrorMessage = ({ export const getValidate = ({ config, errorMessages, - name, + headName, mainSchema, serviceFieldName, setValidationCache, @@ -157,15 +166,17 @@ export const getValidate = ({ const ajvValidate = getAjvValidate({config, mainSchema}); return (_value, allValues, meta) => { - ajvValidate(get(allValues, name)); + ajvValidate(get(allValues, headName)); if (!ajvValidate.errors?.length) { return false; } + const result = {[ARRAY_AND_OBJECT_ERRORS]: {}}; + const waiters: Record = {}; - const ajvErrors: {instancePath: string; error: SyncValidateError}[] = []; - const entityParametersErrors: {instancePath: string; error: SyncValidateError}[] = []; + const ajvErrors: {path: string[]; error: SyncValidateError}[] = []; + const entityParametersErrors: {path: string[]; error: SyncValidateError}[] = []; ajvValidate.errors.forEach((ajvOrEntityParametersError) => { if (ajvOrEntityParametersError.keyword === 'entityParameters') { @@ -180,7 +191,10 @@ export const getValidate = ({ if (cacheItem?.result) { entityParametersErrors.push({ - instancePath: entityParametersError.instancePath, + path: [ + ...parseFinalFormPath(headName), + ...parseInstancePath(entityParametersError.instancePath), + ], error: cacheItem.result, }); } else if (!waiter || !isEqual(entityParametersError.params, waiter)) { @@ -205,7 +219,10 @@ export const getValidate = ({ }); } else { entityParametersErrors.push({ - instancePath: entityParametersError.instancePath, + path: [ + ...parseFinalFormPath(headName), + ...parseInstancePath(entityParametersError.instancePath), + ], error: errorOrPromise, }); } @@ -225,7 +242,10 @@ export const getValidate = ({ } ajvErrors.push({ - instancePath, + path: [ + ...parseFinalFormPath(headName), + ...parseInstancePath(ajvError.instancePath), + ], error: getAjvErrorMessage({ ajvErrorMessage: ajvError.message, errorMessages, @@ -242,25 +262,36 @@ export const getValidate = ({ setValidationWaiters({name: serviceFieldName, waiters}); } - const result = {[ARRAY_AND_OBJECT_ERRORS]: {}}; + const errorsState: ErrorsState | undefined = meta?.data; + const externalRegularErrors: {path: string[]; error: SyncValidateError}[] = Object.values( + mapValues(errorsState?.regularErrors, (value, key) => ({ + path: parseFinalFormPath(key), + error: value, + })), + ); + const externalPriorityErrors: {path: string[]; error: SyncValidateError}[] = Object.values( + mapValues(errorsState?.priorityErrors, (value, key) => ({ + path: parseFinalFormPath(key), + error: value, + })), + ); - [...ajvErrors, ...entityParametersErrors].forEach((item) => { + [ + ...externalRegularErrors, + ...ajvErrors, + ...entityParametersErrors, + ...externalPriorityErrors, + ].forEach((item) => { if (item.error) { - const itemSchema = getSchemaByInstancePath(item.instancePath, mainSchema); + const itemSchema = getSchemaByFinalFormPath(item.path, headName, mainSchema); if (itemSchema) { - const arrayOrObjectSchema = - itemSchema.type === JsonSchemaType.Array || - itemSchema.type === JsonSchemaType.Object; - - const path = [ - ...parseFinalFormPath(name), - ...parseInstancePath(item.instancePath), - ]; + const arraySchema = itemSchema.type === JsonSchemaType.Array; + const objectSchema = itemSchema.type === JsonSchemaType.Object; set( - arrayOrObjectSchema ? result[ARRAY_AND_OBJECT_ERRORS] : result, - path, + arraySchema || objectSchema ? result[ARRAY_AND_OBJECT_ERRORS] : result, + objectSchema ? [...item.path, OBJECT_ERROR] : item.path, item.error, ); } diff --git a/src/lib/unstable/core/constants.ts b/src/lib/unstable/core/constants.ts index 7d3da4bc..4f81d787 100644 --- a/src/lib/unstable/core/constants.ts +++ b/src/lib/unstable/core/constants.ts @@ -14,3 +14,5 @@ export enum JsonSchemaType { export const EMPTY_OBJECT = {}; export const ARRAY_AND_OBJECT_ERRORS = 'ARRAY_AND_OBJECT_ERRORS'; + +export const OBJECT_ERROR = 'OBJECT_ERROR'; diff --git a/src/lib/unstable/core/index.ts b/src/lib/unstable/core/index.ts index e2523734..cd4dc0b5 100644 --- a/src/lib/unstable/core/index.ts +++ b/src/lib/unstable/core/index.ts @@ -4,3 +4,4 @@ export {JsonSchemaType, SchemaRendererMode} from './constants'; export {schemaRendererMutators} from './mutators'; export * from './types'; export * from './useSchemaRendererField'; +export * from './useSetErrors'; diff --git a/src/lib/unstable/core/mutators/index.ts b/src/lib/unstable/core/mutators/index.ts index fe4a8f90..f2d321e6 100644 --- a/src/lib/unstable/core/mutators/index.ts +++ b/src/lib/unstable/core/mutators/index.ts @@ -1,8 +1,12 @@ import {setValidationCache, setValidationWaiters} from './async-validation'; +import {removeErrors, setErrors} from './set-errors'; export * from './async-validation/types'; +export * from './set-errors/types'; export const schemaRendererMutators = { + removeErrors, + setErrors, setValidationCache, setValidationWaiters, }; diff --git a/src/lib/unstable/core/mutators/set-errors/index.ts b/src/lib/unstable/core/mutators/set-errors/index.ts new file mode 100644 index 00000000..8e4fa797 --- /dev/null +++ b/src/lib/unstable/core/mutators/set-errors/index.ts @@ -0,0 +1,2 @@ +export * from './set-errors'; +export * from './types'; diff --git a/src/lib/unstable/core/mutators/set-errors/set-errors.ts b/src/lib/unstable/core/mutators/set-errors/set-errors.ts new file mode 100644 index 00000000..0818ecea --- /dev/null +++ b/src/lib/unstable/core/mutators/set-errors/set-errors.ts @@ -0,0 +1,40 @@ +import omit from 'lodash/omit'; + +import type {ErrorsState, RemoveErrorsFunction, SetErrorsFunction} from './types'; + +export const setErrors: SetErrorsFunction = ( + [{serviceFieldName, priorityErrors, regularErrors}], + mutableState, +) => { + const errorsState = mutableState.fields[serviceFieldName]?.data as ErrorsState | undefined; + + if (errorsState && (priorityErrors || regularErrors)) { + errorsState.priorityErrors = { + ...errorsState.priorityErrors, + ...(priorityErrors || {}), + }; + errorsState.regularErrors = { + ...errorsState.regularErrors, + ...(regularErrors || {}), + }; + } +}; + +export const removeErrors: RemoveErrorsFunction = ( + [{removeFunctionOrNames, serviceFieldName}], + mutableState, +) => { + const errorsState = mutableState.fields[serviceFieldName]?.data as ErrorsState | undefined; + + if (errorsState && removeFunctionOrNames) { + const {priorityErrors, regularErrors} = Array.isArray(removeFunctionOrNames) + ? { + priorityErrors: omit(errorsState.priorityErrors, removeFunctionOrNames), + regularErrors: omit(errorsState.regularErrors, removeFunctionOrNames), + } + : removeFunctionOrNames(errorsState); + + errorsState.priorityErrors = priorityErrors; + errorsState.regularErrors = regularErrors; + } +}; diff --git a/src/lib/unstable/core/mutators/set-errors/types.ts b/src/lib/unstable/core/mutators/set-errors/types.ts new file mode 100644 index 00000000..df96a757 --- /dev/null +++ b/src/lib/unstable/core/mutators/set-errors/types.ts @@ -0,0 +1,41 @@ +import type {MutableState, Tools} from 'final-form'; + +export interface ErrorsState { + priorityErrors?: { + [key: string]: string | undefined; + }; + regularErrors?: { + [key: string]: string | undefined; + }; +} + +export interface SetErrorsParams { + priorityErrors?: { + [key: string]: string | undefined; + }; + serviceFieldName: string; + regularErrors?: { + [key: string]: string | undefined; + }; +} + +export type SetErrorsFunction> = ( + args: [SetErrorsParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetErrorsMutator = (params: SetErrorsParams) => void; + +export interface RemoveErrorsParams { + removeFunctionOrNames: string[] | ((state: ErrorsState) => ErrorsState); + serviceFieldName: string; +} + +export type RemoveErrorsFunction> = ( + args: [RemoveErrorsParams], + state: MutableState, + tools: Tools, +) => void; + +export type RemoveErrorsMutator = (params: RemoveErrorsParams) => void; diff --git a/src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts b/src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts index 3b1bdf56..25845fe3 100644 --- a/src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts +++ b/src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts @@ -4,8 +4,8 @@ import get from 'lodash/get'; import {type FieldRenderProps, type UseFieldConfig, useField} from 'react-final-form'; import {SchemaRendererContext} from '../SchemaRendererContext'; -import {ARRAY_AND_OBJECT_ERRORS, JsonSchemaType} from '../constants'; -import {getSchemaByFinalFormPath} from '../utils'; +import {ARRAY_AND_OBJECT_ERRORS, JsonSchemaType, OBJECT_ERROR} from '../constants'; +import {getSchemaByFinalFormPath, parseFinalFormPath} from '../utils'; export const useSchemaRendererField = < FieldValue = any, @@ -16,7 +16,7 @@ export const useSchemaRendererField = < config?: UseFieldConfig, ): FieldRenderProps => { const { - name: headName, + headName, schema: mainSchema, serviceFieldName, } = React.useContext(SchemaRendererContext); @@ -24,17 +24,20 @@ export const useSchemaRendererField = < const field = useField(name, config); const serviceField = useField(serviceFieldName, {subscription: {error: true}}); - const arrayOrObjectSchema = React.useMemo(() => { + const {arraySchema, objectSchema} = React.useMemo(() => { const type = getSchemaByFinalFormPath(name, headName, mainSchema)?.type; - return type === JsonSchemaType.Array || type === JsonSchemaType.Object; + return { + arraySchema: type === JsonSchemaType.Array, + objectSchema: type === JsonSchemaType.Object, + }; }, [headName, name, mainSchema]); const error = get( - arrayOrObjectSchema + arraySchema || objectSchema ? get(serviceField.meta.error, ARRAY_AND_OBJECT_ERRORS) : serviceField.meta.error, - name, + objectSchema ? [...parseFinalFormPath(name), OBJECT_ERROR] : name, ); return error ? {...field, meta: {...field.meta, error}} : field; diff --git a/src/lib/unstable/core/useSetErrors/index.ts b/src/lib/unstable/core/useSetErrors/index.ts new file mode 100644 index 00000000..1285d98c --- /dev/null +++ b/src/lib/unstable/core/useSetErrors/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './useSetErrors'; diff --git a/src/lib/unstable/core/useSetErrors/types.ts b/src/lib/unstable/core/useSetErrors/types.ts new file mode 100644 index 00000000..92ef4659 --- /dev/null +++ b/src/lib/unstable/core/useSetErrors/types.ts @@ -0,0 +1,10 @@ +import type {RemoveErrorsParams, SetErrorsParams} from '../mutators'; + +export type SetErrors = (params: Omit) => void; + +export type RemoveErrors = (params: Omit) => void; + +export interface UseSetErrorsReturn { + removeErrors: RemoveErrors; + setErrors: SetErrors; +} diff --git a/src/lib/unstable/core/useSetErrors/useSetErrors.tsx b/src/lib/unstable/core/useSetErrors/useSetErrors.tsx new file mode 100644 index 00000000..d6283235 --- /dev/null +++ b/src/lib/unstable/core/useSetErrors/useSetErrors.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import {useForm} from 'react-final-form'; + +import {SchemaRendererContext} from '../SchemaRendererContext'; + +import type {RemoveErrors, SetErrors, UseSetErrorsReturn} from './types'; + +export const useSetErrors = (): UseSetErrorsReturn => { + const {serviceFieldName} = React.useContext(SchemaRendererContext); + const mutators = useForm().mutators; + + const result = React.useMemo(() => { + const setErrors: SetErrors = (params) => { + mutators.setErrors({ + ...params, + serviceFieldName, + }); + }; + + const removeErrors: RemoveErrors = (params) => { + mutators.removeErrors({ + ...params, + serviceFieldName, + }); + }; + + return {removeErrors, setErrors}; + }, [mutators.removeErrors, mutators.setErrors, serviceFieldName]); + + return result; +}; diff --git a/src/lib/unstable/core/utils.ts b/src/lib/unstable/core/utils.ts index 5257915f..22ac38a6 100644 --- a/src/lib/unstable/core/utils.ts +++ b/src/lib/unstable/core/utils.ts @@ -20,12 +20,12 @@ export const parseFinalFormPath = (finalFormPath: string): string[] => { }; export const getSchemaByFinalFormPath = ( - finalFormPath: string, + finalFormPath: string | string[], finalFormHeadPath: string, mainSchema: JsonSchema, ): JsonSchema | undefined => { if (finalFormPath.length) { - return parseFinalFormPath(finalFormPath) + return (Array.isArray(finalFormPath) ? finalFormPath : parseFinalFormPath(finalFormPath)) .slice(parseFinalFormPath(finalFormHeadPath).length) .reduce((acc: JsonSchema | undefined, segment) => { const type = get(acc, 'type'); diff --git a/src/lib/unstable/kit/Text.tsx b/src/lib/unstable/kit/Text.tsx index 49d2763d..e8ff74b0 100644 --- a/src/lib/unstable/kit/Text.tsx +++ b/src/lib/unstable/kit/Text.tsx @@ -7,6 +7,7 @@ import { } from '@gravity-ui/uikit'; import isNil from 'lodash/isNil'; +import {useSetErrors} from '../core'; import type {JsonSchemaNumber, JsonSchemaString, SimpleViewProps} from '../core/types'; export interface TextProps @@ -24,6 +25,8 @@ const Component = < meta, schema, }: T) => { + const {setErrors, removeErrors} = useSetErrors(); + const props: TextInputBaseProps = { hasClear: true, // ...inputProps, @@ -40,6 +43,33 @@ const Component = < // errorMessage: meta.error, }; + React.useEffect(() => { + if (input.name === 'qwe.test.jajaja.stringMaxLength') { + if (input.value === 'jajaja') { + setErrors({ + priorityErrors: { + [input.name]: 'priorityError', + }, + }); + } else { + // setErrors({ + // priorityErrors: { + // [input.name]: undefined, + // }, + // }); + removeErrors({ + removeFunctionOrNames: [input.name], + }); + // removeErrors({ + // removeFunctionOrNames: (params) => ({ + // ...params, + // priorityErrors: omit(params.priorityErrors, input.name), + // }), + // }); + } + } + }, [input.value]); + // if (spec.viewSpec.type === 'password') { if (schema.entityParameters?.viewType === 'password') { return ;