Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
26b113b
Make validation controlled and async
oandregal Aug 29, 2025
9b1fa80
Remove isItemValid
oandregal Oct 15, 2025
1acbc8f
Document useIsFormValid hook
oandregal Oct 15, 2025
c965fa8
Test useIsFormValid: port all existing test for isItemValid
oandregal Oct 15, 2025
219dd24
Update FieldValidity and FormValidity types
oandregal Oct 15, 2025
3cbe36d
useIsFormValid: update tests
oandregal Oct 15, 2025
744e219
Update docs
oandregal Oct 15, 2025
81579ac
Create getCustomValidity utility
oandregal Oct 15, 2025
9ff587b
Update tests
oandregal Oct 15, 2025
16676bb
Rename useIsFormValid to useFormValidity
oandregal Oct 15, 2025
522dd49
useFormValidity returns validity and isValid
oandregal Oct 15, 2025
b2600d0
Add a test for isValid
oandregal Oct 15, 2025
af2bc15
DataForm: datetime control uses validity prop
oandregal Oct 15, 2025
2121abb
DataForm: date control uses validity prop
oandregal Oct 15, 2025
e067e62
Add changelog
oandregal Oct 15, 2025
f8d2252
DataForm: date control attaches different class depending on type
oandregal Oct 15, 2025
1851f70
Fix unmounting issue with panel dropdown/modal
oandregal Oct 16, 2025
10862ef
getCustomValidity: fall through to elements or custom if validity.req…
oandregal Oct 16, 2025
e80ff81
Update changelog
oandregal Oct 16, 2025
84b0a5d
Update README
oandregal Oct 16, 2025
224089f
Story: better custom Edit
oandregal Oct 16, 2025
b1979b1
Support combined fields.
oandregal Oct 16, 2025
1027148
Make linter happy
oandregal Oct 16, 2025
375c4a8
story: remove unnecessary flags
oandregal Oct 16, 2025
22c8cb7
Move useFormValidity test to proper place
oandregal Oct 16, 2025
12f88bf
Improve useFormValidity hook
oandregal Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
useFormValidity returns validity and isValid
  • Loading branch information
oandregal committed Oct 16, 2025
commit 522dd496439869211bc8721c3247ef68c740cd2c
9 changes: 6 additions & 3 deletions packages/dataviews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,10 @@ Parameters:
- `fields`: the fields config, as described in the "fields" property of DataViews.
- `form`: the form config, as described in the "form" property of DataViews.

Returns an object containing the errors. Each property is a field ID, containing a description of each error type. For example:
Returns an object containing:

- `isValid`: a boolean indicating if the form is valid.
- `validity`: an object containing the errors. Each property is a field ID, containing a description of each error type. For example:

```js
{
Expand All @@ -740,8 +743,8 @@ Returns an object containing the errors. Each property is a field ID, containing
message: 'Value must be one of the elements.' // Optional
},
custom: {
type: 'invalid',
message: 'Custom message'
type: 'validating',
message: 'Validating...'
}
}
}
Expand Down
198 changes: 126 additions & 72 deletions packages/dataviews/src/hooks/test/use-form-validity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'valid_order' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );

it( 'fields can override the defaults', () => {
Expand All @@ -46,10 +49,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'order' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );

describe( 'isValid.required', () => {
Expand All @@ -68,10 +74,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'tags' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.tags ).toEqual( REQUIRED_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.tags ).toEqual( REQUIRED_MESSAGE );
expect( isValid ).toBe( false );
} );

it( 'array is invalid when required but not an array', () => {
Expand All @@ -86,10 +95,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'tags' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.tags ).toEqual( REQUIRED_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.tags ).toEqual( REQUIRED_MESSAGE );
expect( isValid ).toBe( false );
} );

it( 'array is valid when required and has values', () => {
Expand All @@ -104,10 +116,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'tags' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );
} );

Expand All @@ -130,10 +145,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'author' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.author ).toEqual( ELEMENTS_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.author ).toEqual( ELEMENTS_MESSAGE );
expect( isValid ).toBe( false );
} );

it( 'text is valid when value is one of the elements', () => {
Expand All @@ -152,10 +170,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'status' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );

it( 'text is invalid when value is not one of the elements', () => {
Expand All @@ -174,10 +195,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'status' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.status ).toEqual( ELEMENTS_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.status ).toEqual( ELEMENTS_MESSAGE );
expect( isValid ).toBe( false );
} );

it( 'integer is valid when value is one of the elements', () => {
Expand All @@ -197,10 +221,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'priority' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );

it( 'integer is invalid when value is not one of the elements', () => {
Expand All @@ -220,10 +247,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'priority' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.priority ).toEqual( ELEMENTS_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.priority ).toEqual( ELEMENTS_MESSAGE );
expect( isValid ).toBe( false );
} );

it( 'number is invalid if value is not one of the elements', () => {
Expand All @@ -239,10 +269,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'price' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.price ).toEqual( ELEMENTS_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.price ).toEqual( ELEMENTS_MESSAGE );
expect( isValid ).toBe( false );
} );

it( 'array is valid if all items are part of the elements', () => {
Expand All @@ -259,10 +292,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'tags' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );

it( 'array is invalid when not all items are part of the elements', () => {
Expand All @@ -279,10 +315,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'tags' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.tags ).toEqual( ELEMENTS_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.tags ).toEqual( ELEMENTS_MESSAGE );
expect( isValid ).toBe( false );
} );

it( 'array is invalid when value is not an array', () => {
Expand All @@ -301,10 +340,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'tags' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.tags ).toEqual( ELEMENTS_MESSAGE );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.tags ).toEqual( ELEMENTS_MESSAGE );
expect( isValid ).toBe( false );
} );
} );

Expand All @@ -318,10 +360,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'order' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );

it( 'integer is invalid if value is not integer when not empty', () => {
Expand All @@ -333,15 +378,18 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'order' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.order ).toEqual( {
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.order ).toEqual( {
custom: {
type: 'invalid',
message: 'Value must be an integer.',
},
} );
expect( isValid ).toBe( false );
} );

it( 'number is valid if value is finite', () => {
Expand All @@ -353,10 +401,13 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'price' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current ).toEqual( undefined );
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity ).toEqual( undefined );
expect( isValid ).toBe( true );
} );

it( 'number is invalid if value is not finite when not empty', () => {
Expand All @@ -368,15 +419,18 @@ describe( 'useFormValidity', () => {
},
];
const form = { fields: [ 'price' ] };
const { result } = renderHook( () =>
useFormValidity( item, fields, form )
);
expect( result.current?.price ).toEqual( {
const {
result: {
current: { validity, isValid },
},
} = renderHook( () => useFormValidity( item, fields, form ) );
expect( validity?.price ).toEqual( {
custom: {
type: 'invalid',
message: 'Value must be a number.',
},
} );
expect( isValid ).toBe( false );
} );
} );
} );
13 changes: 11 additions & 2 deletions packages/dataviews/src/hooks/use-form-validity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function useFormValidity< Item >(
item: Item,
fields: Field< Item >[],
form: Form
): FormValidity {
): { validity: FormValidity; isValid: boolean } {
const [ formValidity, setFormValidity ] = useState< FormValidity >();

const previousValidatedValuesRef = useRef< Record< string, any > >( {} );
Expand Down Expand Up @@ -287,7 +287,16 @@ export function useFormValidity< Item >(
validate();
}, [ validate ] );

return formValidity;
return {
validity: formValidity,
isValid:
! formValidity ||
Object.values( formValidity ).every( ( fieldValidation ) =>
Object.values( fieldValidation ).every(
( validation ) => validation.type === 'valid'
)
),
};
}

export default useFormValidity;
Loading