Skip to content

Commit bdb7f5a

Browse files
Expose title property for validated fields (#4700)
* Expose title property for validated fields * Update docs and examples with new exposed title property * Change documentation entry wording * Remove missed debugger statement in playground * Fix unit tests and update CHANGELOG.md * Update changelog
1 parent bd54461 commit bdb7f5a

File tree

10 files changed

+60
-1
lines changed

10 files changed

+60
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ it according to semantic versioning. For example, if your PR adds a breaking cha
1515
should change the heading of the (upcoming) version to include a major version bump.
1616
1717
-->
18-
1918
# 6.0.0-beta.14
2019

2120
## @rjsf/core
@@ -26,6 +25,7 @@ should change the heading of the (upcoming) version to include a major version b
2625
## @rjsf/utils
2726

2827
- Updated `UiSchema` type to support dynamic array item UI schemas - the `items` property can now be either a `UiSchema` object or a function that returns a `UiSchema` ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
28+
- Added `title` property to `RJSFValidationError` [PR](https://github.com/rjsf-team/react-jsonschema-form/pull/4700)
2929

3030
## @rjsf/chakra-ui
3131

@@ -56,6 +56,10 @@ should change the heading of the (upcoming) version to include a major version b
5656
- Added comprehensive documentation for dynamic UI schema feature with TypeScript examples ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
5757
- Updated array documentation to reference the new dynamic UI schema capabilities ([#4706](https://github.com/rjsf-team/react-jsonschema-form/pull/4706))
5858

59+
## @rjsf/validator-ajv8
60+
61+
- Updated `transformRJSFValidationErrors()` to include the `title` property of a field with error fixing #4504 with [PR](https://github.com/rjsf-team/react-jsonschema-form/pull/4700)
62+
5963
# 6.0.0-beta.13
6064

6165
## @rjsf/shadcn

packages/core/test/ArrayField.test.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,7 @@ describe('ArrayField', () => {
10741074
property: '.1',
10751075
schemaPath: '#/items/type',
10761076
stack: '.1 must be integer',
1077+
title: '',
10771078
},
10781079
],
10791080
formData: [1, null, 3],
@@ -1090,6 +1091,7 @@ describe('ArrayField', () => {
10901091
property: '.1',
10911092
schemaPath: '#/items/type',
10921093
stack: '.1 must be integer',
1094+
title: '',
10931095
},
10941096
]);
10951097
});
@@ -1291,6 +1293,7 @@ describe('ArrayField', () => {
12911293
property: '.multipleChoicesList',
12921294
schemaPath: '#/properties/multipleChoicesList/minItems',
12931295
stack: '.multipleChoicesList must NOT have fewer than 3 items',
1296+
title: '',
12941297
},
12951298
]);
12961299
});

packages/core/test/Form.test.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,7 @@ describeRepeated('Form common', (createFormComponent) => {
17801780
property: '.shipping_address.street_address',
17811781
schemaPath: '#/properties/shipping_address/required',
17821782
stack: "must have required property 'street_address'",
1783+
title: '',
17831784
},
17841785
{
17851786
message: "must have required property 'city'",
@@ -1788,6 +1789,7 @@ describeRepeated('Form common', (createFormComponent) => {
17881789
property: '.shipping_address.city',
17891790
schemaPath: '#/properties/shipping_address/required',
17901791
stack: "must have required property 'city'",
1792+
title: '',
17911793
},
17921794
{
17931795
message: "must have required property 'state'",
@@ -1796,6 +1798,7 @@ describeRepeated('Form common', (createFormComponent) => {
17961798
property: '.shipping_address.state',
17971799
schemaPath: '#/properties/shipping_address/required',
17981800
stack: "must have required property 'state'",
1801+
title: '',
17991802
},
18001803
]);
18011804
});
@@ -1852,6 +1855,7 @@ describeRepeated('Form common', (createFormComponent) => {
18521855
property: '.albums',
18531856
schemaPath: '#/properties/albums/minItems',
18541857
stack: "'Album Titles' must NOT have fewer than 3 items",
1858+
title: 'Album Titles',
18551859
},
18561860
]);
18571861
});
@@ -2061,6 +2065,7 @@ describeRepeated('Form common', (createFormComponent) => {
20612065
property: '',
20622066
schemaPath: '#/type',
20632067
stack: 'must be number',
2068+
title: '',
20642069
},
20652070
]);
20662071
});
@@ -2544,6 +2549,7 @@ describeRepeated('Form common', (createFormComponent) => {
25442549
property: '',
25452550
schemaPath: '#/minLength',
25462551
stack: 'must NOT have fewer than 8 characters',
2552+
title: '',
25472553
},
25482554
]);
25492555
sinon.assert.calledOnce(onError);
@@ -2606,6 +2612,7 @@ describeRepeated('Form common', (createFormComponent) => {
26062612
property: '',
26072613
schemaPath: '#/minLength',
26082614
stack: 'must NOT have fewer than 8 characters',
2615+
title: '',
26092616
},
26102617
]);
26112618
});
@@ -2642,6 +2649,7 @@ describeRepeated('Form common', (createFormComponent) => {
26422649
property: '',
26432650
schemaPath: '#/minLength',
26442651
stack: 'must NOT have fewer than 8 characters',
2652+
title: '',
26452653
},
26462654
{
26472655
message: 'must match pattern "d+"',
@@ -2650,6 +2658,7 @@ describeRepeated('Form common', (createFormComponent) => {
26502658
property: '',
26512659
schemaPath: '#/pattern',
26522660
stack: 'must match pattern "d+"',
2661+
title: '',
26532662
},
26542663
]);
26552664
});
@@ -2702,6 +2711,7 @@ describeRepeated('Form common', (createFormComponent) => {
27022711
property: '.level1.level2',
27032712
schemaPath: '#/properties/level1/properties/level2/minLength',
27042713
stack: '.level1.level2 must NOT have fewer than 8 characters',
2714+
title: '',
27052715
},
27062716
]);
27072717
});
@@ -2742,6 +2752,7 @@ describeRepeated('Form common', (createFormComponent) => {
27422752
property: '.1',
27432753
schemaPath: '#/items/minLength',
27442754
stack: '.1 must NOT have fewer than 4 characters',
2755+
title: '',
27452756
},
27462757
]);
27472758
});
@@ -2798,6 +2809,7 @@ describeRepeated('Form common', (createFormComponent) => {
27982809
property: '.level1.1',
27992810
schemaPath: '#/properties/level1/items/minLength',
28002811
stack: '.level1.1 must NOT have fewer than 4 characters',
2812+
title: '',
28012813
},
28022814
{
28032815
message: 'must NOT have fewer than 4 characters',
@@ -2806,6 +2818,7 @@ describeRepeated('Form common', (createFormComponent) => {
28062818
property: '.level1.3',
28072819
schemaPath: '#/properties/level1/items/minLength',
28082820
stack: '.level1.3 must NOT have fewer than 4 characters',
2821+
title: '',
28092822
},
28102823
]);
28112824
});
@@ -2871,6 +2884,7 @@ describeRepeated('Form common', (createFormComponent) => {
28712884
property: '.outer.0.1',
28722885
schemaPath: '#/properties/outer/items/items/minLength',
28732886
stack: '.outer.0.1 must NOT have fewer than 4 characters',
2887+
title: '',
28742888
},
28752889
{
28762890
message: 'must NOT have fewer than 4 characters',
@@ -2879,6 +2893,7 @@ describeRepeated('Form common', (createFormComponent) => {
28792893
property: '.outer.1.0',
28802894
schemaPath: '#/properties/outer/items/items/minLength',
28812895
stack: '.outer.1.0 must NOT have fewer than 4 characters',
2896+
title: '',
28822897
},
28832898
]);
28842899
sinon.assert.calledOnce(focusSpy);
@@ -2933,6 +2948,7 @@ describeRepeated('Form common', (createFormComponent) => {
29332948
property: '.1.foo',
29342949
schemaPath: '#/items/properties/foo/minLength',
29352950
stack: '.1.foo must NOT have fewer than 4 characters',
2951+
title: '',
29362952
},
29372953
]);
29382954
});
@@ -3558,6 +3574,7 @@ describeRepeated('Form common', (createFormComponent) => {
35583574
property: '.areaCode',
35593575
schemaPath: '#/properties/areaCode/format',
35603576
stack: '.areaCode must match format "area-code"',
3577+
title: '',
35613578
},
35623579
]);
35633580
// We use setTimeout with a delay of 0ms to allow all asynchronous operations to complete in the React component.
@@ -3606,6 +3623,7 @@ describeRepeated('Form common', (createFormComponent) => {
36063623
property: '',
36073624
schemaPath: '#/minLength',
36083625
stack: 'must NOT have fewer than 8 characters',
3626+
title: '',
36093627
},
36103628
{
36113629
message: 'must match pattern "d+"',
@@ -3614,6 +3632,7 @@ describeRepeated('Form common', (createFormComponent) => {
36143632
property: '',
36153633
schemaPath: '#/pattern',
36163634
stack: 'must match pattern "d+"',
3635+
title: '',
36173636
},
36183637
]);
36193638
});
@@ -4648,6 +4667,7 @@ describe('Form omitExtraData and liveOmit', () => {
46484667
},
46494668
schemaPath: '#/required',
46504669
stack: "must have required property 'foo'",
4670+
title: '',
46514671
},
46524672
]);
46534673

packages/core/test/ObjectField.test.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,7 @@ describe('ObjectField', () => {
714714
property: '',
715715
schemaPath: '#/additionalProperties',
716716
stack: 'must NOT have additional properties',
717+
title: '',
717718
},
718719
]);
719720
});

packages/core/test/StringField.test.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,7 @@ describe('StringField', () => {
808808
property: '',
809809
schemaPath: '#/type',
810810
stack: 'must be string',
811+
title: '',
811812
},
812813
],
813814
});
@@ -843,6 +844,7 @@ describe('StringField', () => {
843844
property: '',
844845
schemaPath: '#/type',
845846
stack: 'must be string',
847+
title: '',
846848
},
847849
],
848850
});
@@ -1010,6 +1012,7 @@ describe('StringField', () => {
10101012
property: '',
10111013
schemaPath: '#/format',
10121014
stack: 'must match format "date"',
1015+
title: '',
10131016
},
10141017
],
10151018
});
@@ -1141,6 +1144,7 @@ describe('StringField', () => {
11411144
property: '',
11421145
schemaPath: '#/format',
11431146
stack: 'must match format "time"',
1147+
title: '',
11441148
},
11451149
],
11461150
});
@@ -1948,6 +1952,7 @@ describe('StringField', () => {
19481952
property: '',
19491953
schemaPath: '#/format',
19501954
stack: 'must match format "email"',
1955+
title: '',
19511956
},
19521957
],
19531958
});
@@ -2088,6 +2093,7 @@ describe('StringField', () => {
20882093
property: '',
20892094
schemaPath: '#/format',
20902095
stack: 'must match format "uri"',
2096+
title: '',
20912097
},
20922098
],
20932099
});
@@ -2209,6 +2215,7 @@ describe('StringField', () => {
22092215
property: '',
22102216
schemaPath: '#/format',
22112217
stack: 'must match format "color"',
2218+
title: '',
22122219
},
22132220
],
22142221
});

packages/core/test/validate.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe('Validation', () => {
5050
property: 'foo',
5151
schemaPath: '#/required',
5252
stack: "must have required property 'foo'",
53+
title: '',
5354
},
5455
]);
5556
});
@@ -102,6 +103,7 @@ describe('Validation', () => {
102103
property: '.foo',
103104
schemaPath: '#/properties/foo/minLength',
104105
stack: '.foo must NOT have fewer than 10 characters',
106+
title: '',
105107
},
106108
]);
107109
});
@@ -249,6 +251,7 @@ describe('Validation', () => {
249251
property: '.pass2',
250252
schemaPath: '#/properties/pass2/minLength',
251253
stack: '.pass2 must NOT have fewer than 3 characters',
254+
title: '',
252255
},
253256
{
254257
property: '.pass2',
@@ -372,6 +375,7 @@ describe('Validation', () => {
372375
property: 'foo',
373376
schemaPath: '#/required',
374377
stack: "must have required property 'foo'",
378+
title: '',
375379
},
376380
]);
377381
});
@@ -467,6 +471,7 @@ describe('Validation', () => {
467471
property: '.datasetId',
468472
schemaPath: '#/properties/datasetId/pattern',
469473
stack: '.datasetId must match pattern "\\d+"',
474+
title: '',
470475
},
471476
]);
472477
onError.resetHistory();

packages/docs/docs/usage/validation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ Each element in the `errors` list passed to `transformErrors` is a `RJSFValidati
382382
- `property`: optional string in Javascript property accessor notation to the data path of the field with the error. For example, `.name` or `.first-name`.
383383
- `schemaPath`: optional JSON pointer to the schema of the keyword that failed validation. For example, `#/fields/firstName/required`. (Note: this may sometimes be wrong due to a [bug in ajv](https://github.com/ajv-validator/ajv/issues/512)).
384384
- `stack`: full error name, for example ".name is a required property".
385+
- `title`: optional string containing title property for the field with error.
385386

386387
## Error List Display
387388

packages/playground/src/samples/validation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const transformErrors: ErrorTransformer = (errors) => {
1515
message: 'You need to be 18 because of some legal thing',
1616
});
1717
}
18+
if (error.name === 'required') {
19+
return Object.assign({}, error, {
20+
message: `${error.title} is a required field`,
21+
});
22+
}
1823
return error;
1924
});
2025
};
@@ -25,7 +30,12 @@ const validation: Sample = {
2530
description:
2631
'This form defines custom validation rules checking that the two passwords match. There is also a custom validation message when submitting an age < 18, which can only be seen if HTML5 validation is turned off.',
2732
type: 'object',
33+
required: ['firstName'],
2834
properties: {
35+
firstName: {
36+
title: 'First Name',
37+
type: 'string',
38+
},
2939
pass1: {
3040
title: 'Password',
3141
type: 'string',

packages/utils/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ export type RJSFValidationError = {
212212
schemaPath?: string;
213213
/** Full error name, for example ".name is a required property" */
214214
stack: string;
215+
/** The title property for the failing field*/
216+
title?: string;
215217
};
216218

217219
/** The type that describes an error in a field */

packages/validator-ajv8/src/processRawValidationErrors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export function transformRJSFValidationErrors<
3939
let { message = '' } = rest;
4040
let property = instancePath.replace(/\//g, '.');
4141
let stack = `${property} ${message}`.trim();
42+
let uiTitle = '';
4243
const rawPropertyNames: string[] = [
4344
...(params.deps?.split(', ') || []),
4445
params.missingProperty,
@@ -61,10 +62,12 @@ export function transformRJSFValidationErrors<
6162
}
6263
if (uiSchemaTitle) {
6364
message = message.replace(`'${currentProperty}'`, `'${uiSchemaTitle}'`);
65+
uiTitle = uiSchemaTitle;
6466
} else {
6567
const parentSchemaTitle = get(parentSchema, [PROPERTIES_KEY, currentProperty, 'title']);
6668
if (parentSchemaTitle) {
6769
message = message.replace(`'${currentProperty}'`, `'${parentSchemaTitle}'`);
70+
uiTitle = parentSchemaTitle;
6871
}
6972
}
7073
});
@@ -75,11 +78,13 @@ export function transformRJSFValidationErrors<
7578

7679
if (uiSchemaTitle) {
7780
stack = `'${uiSchemaTitle}' ${message}`.trim();
81+
uiTitle = uiSchemaTitle;
7882
} else {
7983
const parentSchemaTitle = parentSchema?.title;
8084

8185
if (parentSchemaTitle) {
8286
stack = `'${parentSchemaTitle}' ${message}`.trim();
87+
uiTitle = parentSchemaTitle;
8388
}
8489
}
8590
}
@@ -97,6 +102,7 @@ export function transformRJSFValidationErrors<
97102
params, // specific to ajv
98103
stack,
99104
schemaPath,
105+
title: uiTitle,
100106
};
101107
});
102108
}

0 commit comments

Comments
 (0)