Skip to content

Commit

Permalink
feat: add external errors (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
bocembocem authored Nov 14, 2023
1 parent 1e19619 commit 4c93b37
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/lib/core/components/Form/Controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const Controller = <Value extends FieldValue, SpecType extends Spec>({
parentOnChange,
parentOnUnmount,
}: ControllerProps<Value, SpecType>) => {
const {tools, __mirror} = useDynamicFormsCtx();
const {tools, externalErrors, __mirror} = useDynamicFormsCtx();
const {inputEntity, Layout} = useComponents(spec);
const render = useRender({name, spec, inputEntity, Layout});
const validate = useValidate(spec);
Expand All @@ -47,6 +47,7 @@ export const Controller = <Value extends FieldValue, SpecType extends Spec>({
tools,
parentOnChange,
parentOnUnmount,
externalErrors,
});
const withSearch = useSearch(spec, renderProps.input.value, name);

Expand Down
7 changes: 5 additions & 2 deletions src/lib/core/components/Form/DynamicField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
useSearchStore,
useStore,
} from './hooks';
import {DynamicFormConfig, FieldValue, WonderMirror} from './types';
import {BaseValidateError, DynamicFormConfig, FieldValue, WonderMirror} from './types';
import {getDefaultSearchFunction, isCorrectConfig} from './utils';

export interface DynamicFieldProps {
Expand All @@ -27,6 +27,7 @@ export interface DynamicFieldProps {
search?: string | ((spec: Spec, input: FieldValue, name: string) => boolean);
generateRandomValue?: (spec: StringSpec) => string;
withoutInsertFFDebounce?: boolean;
errors?: Record<string, BaseValidateError>;
__mirror?: WonderMirror;
}

Expand All @@ -38,6 +39,7 @@ export const DynamicField: React.FC<DynamicFieldProps> = ({
generateRandomValue,
search,
withoutInsertFFDebounce,
errors: externalErrors,
__mirror,
}) => {
const DynamicFormsCtx = useCreateContext();
Expand All @@ -52,9 +54,10 @@ export const DynamicField: React.FC<DynamicFieldProps> = ({
Monaco: isValidElementType(Monaco) ? Monaco : undefined,
generateRandomValue,
tools,
externalErrors,
__mirror,
}),
[tools, config, Monaco, __mirror, generateRandomValue],
[tools, config, Monaco, __mirror, generateRandomValue, externalErrors],
);

const searchContext = React.useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('Form/hooks/useField', () => {

expect(mirror.field.useStore?.store.values[name]).not.toBe(value[name]);
expect(mirror.field.useStore?.store.values[name]).toMatchObject(value[name]);
expect(mirror.field.useStore?.store.errors[name]).toBe(false);
expect(mirror.field.useStore?.store.errors[name]).toBe(undefined);
});

test('initialization with required object spec', () => {
Expand Down Expand Up @@ -117,7 +117,7 @@ describe('Form/hooks/useField', () => {
expect(mirror.controller[name]?.useField?.arrayInput.value).toMatchObject(objectDefault);

expect(mirror.field.useStore?.store.values[name]).toMatchObject(objectDefault);
expect(mirror.field.useStore?.store.errors[name]).toBe(false);
expect(mirror.field.useStore?.store.errors[name]).toBe(undefined);
});

test('initialization with required array spec', () => {
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('Form/hooks/useField', () => {
expect(mirror.controller[name]?.useField?.arrayInput.value).toMatchObject(arrayDefault);

expect(mirror.field.useStore?.store.values[name]).toMatchObject(arrayDefault);
expect(mirror.field.useStore?.store.errors[name]).toBe(false);
expect(mirror.field.useStore?.store.errors[name]).toBe(undefined);
});

test('initialization with error', () => {
Expand Down Expand Up @@ -498,7 +498,7 @@ describe('Form/hooks/useField', () => {
);

expect(mirror.field.useStore?.store.values[name]).toMatchObject({});
expect(mirror.field.useStore?.store.errors[name]).toBe(false);
expect(mirror.field.useStore?.store.errors[name]).toBe(undefined);

rerender(
<Form initialValues={{}} onSubmit={_.noop}>
Expand Down
36 changes: 35 additions & 1 deletion src/lib/core/components/Form/hooks/useField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {isArraySpec, isNumberSpec, isObjectSpec} from '../../../helpers';
import {Spec} from '../../../types';
import {OBJECT_ARRAY_CNT, OBJECT_ARRAY_FLAG} from '../constants';
import {
BaseValidateError,
DynamicFormsContext,
FieldArrayValue,
FieldObjectValue,
Expand All @@ -30,6 +31,7 @@ export interface UseFieldProps<Value extends FieldValue, SpecType extends Spec>
) => void)
| null;
parentOnUnmount: ((childName: string) => void) | null;
externalErrors?: Record<string, BaseValidateError>;
}

export const useField = <Value extends FieldValue, SpecType extends Spec>({
Expand All @@ -41,6 +43,7 @@ export const useField = <Value extends FieldValue, SpecType extends Spec>({
tools,
parentOnChange,
parentOnUnmount: externalParentOnUnmount,
externalErrors,
}: UseFieldProps<Value, SpecType>): FieldRenderProps<Value> => {
const firstRenderRef = React.useRef(true);

Expand All @@ -67,7 +70,19 @@ export const useField = <Value extends FieldValue, SpecType extends Spec>({
}
}

const error = validate?.(value);
let externalError = _.get(externalErrors, name);

if (
!(
_.isString(externalError) ||
_.isBoolean(externalError) ||
_.isUndefined(externalError)
)
) {
externalError = undefined;
}

const error = validate?.(value) || externalError;
const dirty = !_.isEqual(value, initialValue);

return {
Expand Down Expand Up @@ -254,6 +269,25 @@ export const useField = <Value extends FieldValue, SpecType extends Spec>({
}
}, [state.value]);

React.useEffect(() => {
const externalError = _.get(externalErrors, name);

if (
!firstRenderRef.current &&
(_.isString(externalError) ||
_.isBoolean(externalError) ||
_.isUndefined(externalError)) &&
state.error !== externalError &&
!(state.error && !externalError)
) {
setState({...state, error: externalError});
(parentOnChange ? parentOnChange : tools.onChange)(name, state.value, {
...state.childErrors,
[name]: externalError,
});
}
}, [externalErrors]);

React.useEffect(() => {
firstRenderRef.current = false;

Expand Down
3 changes: 2 additions & 1 deletion src/lib/core/components/Form/types/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {MonacoEditorProps} from 'react-monaco-editor/lib/types';

import {StringSpec} from '../../../types';

import {DynamicFormConfig, FieldValue, ValidateError, WonderMirror} from './';
import {BaseValidateError, DynamicFormConfig, FieldValue, ValidateError, WonderMirror} from './';

export interface DynamicFormsContext {
config: DynamicFormConfig;
Expand All @@ -16,5 +16,6 @@ export interface DynamicFormsContext {
onUnmount: (name: string) => void;
submitFailed: boolean;
};
externalErrors?: Record<string, BaseValidateError>;
__mirror?: WonderMirror;
}

0 comments on commit 4c93b37

Please sign in to comment.