@@ -14,6 +14,7 @@ import type {JSONObject} from '@/types';
1414import { buildValidationSchema , useValidationSchemas , validatePlugins } from '@/validationSchema' ;
1515import { deepMergeValues , extractInitialValues } from '@/values' ;
1616import { processVisibility } from '@/visibility' ;
17+ import type { Errors } from '@/visibility' ;
1718
1819import ItemPreview from './ItemPreview' ;
1920import isEmpty from './empty' ;
@@ -47,6 +48,7 @@ export interface ItemBodyProps {
4748 parentComponentsMap : Record < string , AnyComponentSchema > ;
4849 initialValues : JSONObject ;
4950 onItemValuesUpdated : ( itemValues : JSONObject ) => void ;
51+ onItemErrorsUpdated : ( itemErrors : Errors ) => void ;
5052 onValidationSchemaChange : ( index : number , schema : z . ZodSchema < JSONObject > ) => void ;
5153 expanded : boolean ;
5254}
@@ -61,11 +63,12 @@ const ItemBody: React.FC<ItemBodyProps> = ({
6163 parentComponentsMap,
6264 initialValues,
6365 onItemValuesUpdated,
66+ onItemErrorsUpdated,
6467 onValidationSchemaChange,
6568 expanded,
6669} ) => {
6770 const intl = useIntl ( ) ;
68- const { values} = useFormikContext < WrappedJSONObject > ( ) ;
71+ const { values, errors } = useFormikContext < WrappedJSONObject > ( ) ;
6972 const { validatePluginCallback} = useFormSettings ( ) ;
7073
7174 // ensure we peek deep inside the formik data skipping over any prefixes applied by
@@ -75,6 +78,7 @@ const ItemBody: React.FC<ItemBodyProps> = ({
7578 if ( ! rawNamePrefix . endsWith ( '.' ) ) throw new Error ( 'Unexpected name prefix' ) ;
7679 const namePrefix = rawNamePrefix . slice ( 0 , - 1 ) ;
7780 const itemValues : JSONObject = getIn ( values , namePrefix ) ;
81+ const itemErrors : Errors = getIn ( errors , namePrefix ) ;
7882
7983 const componentsMap = useMemo ( ( ) => {
8084 const localComponentsMap : Record < string , AnyComponentSchema > = Object . fromEntries (
@@ -88,33 +92,33 @@ const ItemBody: React.FC<ItemBodyProps> = ({
8892 return { ...parentComponentsMap , ...localComponentsMap } ;
8993 } , [ parentComponentsMap , components , parentKey ] ) ;
9094
91- const { visibleComponents, updatedItemValues} = useMemo ( ( ) => {
92- const { visibleComponents , updatedValues : updatedItemValues } = processVisibility (
93- components ,
94- itemValues ,
95- {
96- // in this case, the parent is the item itself rather than the `editgrid`
97- // component. There are no mechanisms to hide an entire item. If the editgrid
98- // component were to be hidden, matching key of that component will be cleared
99- // and/or items won't be rendered at all because the editgrid component is
100- // filtered out of the visible components.
101- parentHidden : false ,
102- initialValues ,
103- getRegistryEntry ,
104- getEvaluationScope : ( values : JSONObject ) : JSONObject => {
105- const result : JSONObject = setIn ( parentValues , parentKey , values ) ;
106- return result ;
107- } ,
108- componentsMap ,
109- }
110- ) ;
95+ const { visibleComponents, updatedItemValues, updatedItemErrors } = useMemo ( ( ) => {
96+ const {
97+ visibleComponents ,
98+ updatedValues : updatedItemValues ,
99+ updatedErrors : updatedItemErrors ,
100+ } = processVisibility ( components , itemValues , itemErrors , {
101+ // in this case, the parent is the item itself rather than the ` editgrid`
102+ // component. There are no mechanisms to hide an entire item. If the editgrid
103+ // component were to be hidden, matching key of that component will be cleared
104+ // and/or items won't be rendered at all because the editgrid component is
105+ // filtered out of the visible components.
106+ parentHidden : false ,
107+ initialValues ,
108+ getRegistryEntry ,
109+ getEvaluationScope : ( values : JSONObject ) : JSONObject => {
110+ const result : JSONObject = setIn ( parentValues , parentKey , values ) ;
111+ return result ;
112+ } ,
113+ componentsMap ,
114+ } ) ;
111115 const updatedValidationSchema = buildValidationSchema ( visibleComponents , {
112116 intl,
113117 getRegistryEntry,
114118 validatePlugins : validatePlugins . bind ( null , validatePluginCallback ) ,
115119 } ) ;
116120 onValidationSchemaChange ( index , updatedValidationSchema ) ;
117- return { visibleComponents, updatedItemValues} ;
121+ return { visibleComponents, updatedItemValues, updatedItemErrors } ;
118122 } , [
119123 intl ,
120124 index ,
@@ -127,6 +131,7 @@ const ItemBody: React.FC<ItemBodyProps> = ({
127131 componentsMap ,
128132 initialValues ,
129133 itemValues ,
134+ itemErrors ,
130135 ] ) ;
131136
132137 // handle the side-effects from the visibility checks that apply clearOnHide to the
@@ -141,7 +146,18 @@ const ItemBody: React.FC<ItemBodyProps> = ({
141146 if ( updatedItemValues !== itemValues ) {
142147 onItemValuesUpdated ( updatedItemValues ) ;
143148 }
144- } , [ onItemValuesUpdated , itemValues , updatedItemValues ] ) ;
149+
150+ if ( updatedItemErrors !== itemErrors ) {
151+ onItemErrorsUpdated ( updatedItemErrors ) ;
152+ }
153+ } , [
154+ onItemValuesUpdated ,
155+ itemValues ,
156+ updatedItemValues ,
157+ onItemErrorsUpdated ,
158+ itemErrors ,
159+ updatedItemErrors ,
160+ ] ) ;
145161
146162 if ( ! expanded ) {
147163 // assign the local item values to the editgrid scope - `parentKey` is the key of the
@@ -199,8 +215,13 @@ export const FormioEditGrid: React.FC<EditGridProps> = ({
199215 renderNested : FormioComponent ,
200216 getRegistryEntry,
201217} ) => {
202- const { values, setFieldValue, getFieldProps} = useFormikContext < WrappedJSONObject > ( ) ;
218+ const { values, setFieldValue, getFieldProps, getFieldMeta, setFieldError} =
219+ useFormikContext < WrappedJSONObject > ( ) ;
203220 const { value} = getFieldProps < JSONObject [ ] > ( key ) ;
221+ const { error} = getFieldMeta < JSONObject [ ] > ( key ) ;
222+ // type cast because the FormikErrors type is plain wrong for nested structures like
223+ // edit grids
224+ const fieldError = error as Errors ;
204225
205226 // ensure we peek deep inside the formik data skipping over any prefixes applied by
206227 // the EditGridItem for the isolation-mode-editing. Note that prefix ends with a
@@ -257,6 +278,18 @@ export const FormioEditGrid: React.FC<EditGridProps> = ({
257278 const updatedFieldValue = replace ( value , index , newItemValues ) ;
258279 setFieldValue ( key , updatedFieldValue ) ;
259280 } }
281+ onItemErrorsUpdated = { newItemErrors => {
282+ // we can only replace the item errors if the field errors are an array of
283+ // item-level errors. Possibly there are:
284+ // - no errors at all (undefined)
285+ // - a string error, for the editgrid as a whole
286+ if ( Array . isArray ( fieldError ) ) {
287+ const updatedFieldErrors = replace ( fieldError , index , newItemErrors ) ;
288+ // @ts -expect-error the formik type expects a string, but we're working
289+ // with nested objects here
290+ setFieldError ( key , updatedFieldErrors ) ;
291+ }
292+ } }
260293 onValidationSchemaChange = { setSchema }
261294 expanded = { expanded }
262295 />
0 commit comments