Skip to content

Commit 12a43d4

Browse files
committed
Better warnings for nested propTypes
`arrayOf`, `shape` and `objectOf` warnings now display the full path of the invalid key.
1 parent 05b98ac commit 12a43d4

File tree

3 files changed

+68
-39
lines changed

3 files changed

+68
-39
lines changed

src/addons/link/__tests__/ReactLinkPropTypes-test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,22 @@ describe('ReactLink', function() {
4848
typeCheckFail(
4949
LinkPropTypes.link(React.PropTypes.any),
5050
{},
51-
'Required prop `value` was not specified in `testComponent`.'
51+
'Required prop `testProp.value` was not specified in `testComponent`.'
5252
);
5353
typeCheckFail(
5454
LinkPropTypes.link(React.PropTypes.any),
5555
{value: 123},
56-
'Required prop `requestChange` was not specified in `testComponent`.'
56+
'Required prop `testProp.requestChange` was not specified in `testComponent`.'
5757
);
5858
typeCheckFail(
5959
LinkPropTypes.link(React.PropTypes.any),
6060
{requestChange: emptyFunction},
61-
'Required prop `value` was not specified in `testComponent`.'
61+
'Required prop `testProp.value` was not specified in `testComponent`.'
6262
);
6363
typeCheckFail(
6464
LinkPropTypes.link(React.PropTypes.any),
6565
{value: null, requestChange: null},
66-
'Required prop `value` was not specified in `testComponent`.'
66+
'Required prop `testProp.value` was not specified in `testComponent`.'
6767
);
6868
});
6969

@@ -104,7 +104,7 @@ describe('ReactLink', function() {
104104
typeCheckFail(
105105
LinkPropTypes.link(React.PropTypes.string),
106106
{value: 123, requestChange: emptyFunction},
107-
'Invalid prop `value` of type `number` supplied to `testComponent`,' +
107+
'Invalid prop `testProp.value` of type `number` supplied to `testComponent`,' +
108108
' expected `string`.'
109109
);
110110
});
@@ -148,7 +148,7 @@ describe('ReactLink', function() {
148148
typeCheckFail(
149149
LinkPropTypes.link(React.PropTypes.oneOfType([React.PropTypes.number])),
150150
{value: 'imastring', requestChange: emptyFunction},
151-
'Invalid prop `value` supplied to `testComponent`.'
151+
'Invalid prop `testProp.value` supplied to `testComponent`.'
152152
);
153153
});
154154
});

src/classic/types/ReactPropTypes.js

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -86,19 +86,27 @@ var ReactPropTypes = {
8686
};
8787

8888
function createChainableTypeChecker(validate) {
89-
function checkType(isRequired, props, propName, componentName, location) {
89+
function checkType(
90+
isRequired,
91+
props,
92+
propName,
93+
componentName,
94+
location,
95+
propFullName
96+
) {
9097
componentName = componentName || ANONYMOUS;
98+
propFullName = propFullName || propName;
9199
if (props[propName] == null) {
92100
var locationName = ReactPropTypeLocationNames[location];
93101
if (isRequired) {
94102
return new Error(
95-
`Required ${locationName} \`${propName}\` was not specified in ` +
103+
`Required ${locationName} \`${propFullName}\` was not specified in ` +
96104
`\`${componentName}\`.`
97105
);
98106
}
99107
return null;
100108
} else {
101-
return validate(props, propName, componentName, location);
109+
return validate(props, propName, componentName, location, propFullName);
102110
}
103111
}
104112

@@ -109,7 +117,7 @@ function createChainableTypeChecker(validate) {
109117
}
110118

111119
function createPrimitiveTypeChecker(expectedType) {
112-
function validate(props, propName, componentName, location) {
120+
function validate(props, propName, componentName, location, propFullName) {
113121
var propValue = props[propName];
114122
var propType = getPropType(propValue);
115123
if (propType !== expectedType) {
@@ -120,8 +128,9 @@ function createPrimitiveTypeChecker(expectedType) {
120128
var preciseType = getPreciseType(propValue);
121129

122130
return new Error(
123-
`Invalid ${locationName} \`${propName}\` of type \`${preciseType}\` ` +
124-
`supplied to \`${componentName}\`, expected \`${expectedType}\`.`
131+
`Invalid ${locationName} \`${propFullName}\` of type ` +
132+
`\`${preciseType}\` supplied to \`${componentName}\`, expected ` +
133+
`\`${expectedType}\`.`
125134
);
126135
}
127136
return null;
@@ -134,18 +143,24 @@ function createAnyTypeChecker() {
134143
}
135144

136145
function createArrayOfTypeChecker(typeChecker) {
137-
function validate(props, propName, componentName, location) {
146+
function validate(props, propName, componentName, location, propFullName) {
138147
var propValue = props[propName];
139148
if (!Array.isArray(propValue)) {
140149
var locationName = ReactPropTypeLocationNames[location];
141150
var propType = getPropType(propValue);
142151
return new Error(
143-
`Invalid ${locationName} \`${propName}\` of type ` +
152+
`Invalid ${locationName} \`${propFullName}\` of type ` +
144153
`\`${propType}\` supplied to \`${componentName}\`, expected an array.`
145154
);
146155
}
147156
for (var i = 0; i < propValue.length; i++) {
148-
var error = typeChecker(propValue, i, componentName, location);
157+
var error = typeChecker(
158+
propValue,
159+
i,
160+
componentName,
161+
location,
162+
`${propFullName}[${i}]`
163+
);
149164
if (error instanceof Error) {
150165
return error;
151166
}
@@ -156,11 +171,11 @@ function createArrayOfTypeChecker(typeChecker) {
156171
}
157172

158173
function createElementTypeChecker() {
159-
function validate(props, propName, componentName, location) {
174+
function validate(props, propName, componentName, location, propFullName) {
160175
if (!ReactElement.isValidElement(props[propName])) {
161176
var locationName = ReactPropTypeLocationNames[location];
162177
return new Error(
163-
`Invalid ${locationName} \`${propName}\` supplied to ` +
178+
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
164179
`\`${componentName}\`, expected a single ReactElement.`
165180
);
166181
}
@@ -170,12 +185,12 @@ function createElementTypeChecker() {
170185
}
171186

172187
function createInstanceTypeChecker(expectedClass) {
173-
function validate(props, propName, componentName, location) {
188+
function validate(props, propName, componentName, location, propFullName) {
174189
if (!(props[propName] instanceof expectedClass)) {
175190
var locationName = ReactPropTypeLocationNames[location];
176191
var expectedClassName = expectedClass.name || ANONYMOUS;
177192
return new Error(
178-
`Invalid ${locationName} \`${propName}\` supplied to ` +
193+
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
179194
`\`${componentName}\`, expected instance of \`${expectedClassName}\`.`
180195
);
181196
}
@@ -185,7 +200,7 @@ function createInstanceTypeChecker(expectedClass) {
185200
}
186201

187202
function createEnumTypeChecker(expectedValues) {
188-
function validate(props, propName, componentName, location) {
203+
function validate(props, propName, componentName, location, propFullName) {
189204
var propValue = props[propName];
190205
for (var i = 0; i < expectedValues.length; i++) {
191206
if (propValue === expectedValues[i]) {
@@ -196,27 +211,33 @@ function createEnumTypeChecker(expectedValues) {
196211
var locationName = ReactPropTypeLocationNames[location];
197212
var valuesString = JSON.stringify(expectedValues);
198213
return new Error(
199-
`Invalid ${locationName} \`${propName}\` of value \`${propValue}\` ` +
214+
`Invalid ${locationName} \`${propFullName}\` of value \`${propValue}\` ` +
200215
`supplied to \`${componentName}\`, expected one of ${valuesString}.`
201216
);
202217
}
203218
return createChainableTypeChecker(validate);
204219
}
205220

206221
function createObjectOfTypeChecker(typeChecker) {
207-
function validate(props, propName, componentName, location) {
222+
function validate(props, propName, componentName, location, propFullName) {
208223
var propValue = props[propName];
209224
var propType = getPropType(propValue);
210225
if (propType !== 'object') {
211226
var locationName = ReactPropTypeLocationNames[location];
212227
return new Error(
213-
`Invalid ${locationName} \`${propName}\` of type ` +
228+
`Invalid ${locationName} \`${propFullName}\` of type ` +
214229
`\`${propType}\` supplied to \`${componentName}\`, expected an object.`
215230
);
216231
}
217232
for (var key in propValue) {
218233
if (propValue.hasOwnProperty(key)) {
219-
var error = typeChecker(propValue, key, componentName, location);
234+
var error = typeChecker(
235+
propValue,
236+
key,
237+
componentName,
238+
location,
239+
`${propFullName}.${key}`
240+
);
220241
if (error instanceof Error) {
221242
return error;
222243
}
@@ -228,29 +249,31 @@ function createObjectOfTypeChecker(typeChecker) {
228249
}
229250

230251
function createUnionTypeChecker(arrayOfTypeCheckers) {
231-
function validate(props, propName, componentName, location) {
252+
function validate(props, propName, componentName, location, propFullName) {
232253
for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
233254
var checker = arrayOfTypeCheckers[i];
234-
if (checker(props, propName, componentName, location) == null) {
255+
if (
256+
checker(props, propName, componentName, location, propFullName) == null
257+
) {
235258
return null;
236259
}
237260
}
238261

239262
var locationName = ReactPropTypeLocationNames[location];
240263
return new Error(
241-
`Invalid ${locationName} \`${propName}\` supplied to ` +
264+
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
242265
`\`${componentName}\`.`
243266
);
244267
}
245268
return createChainableTypeChecker(validate);
246269
}
247270

248271
function createNodeChecker() {
249-
function validate(props, propName, componentName, location) {
272+
function validate(props, propName, componentName, location, propFullName) {
250273
if (!isNode(props[propName])) {
251274
var locationName = ReactPropTypeLocationNames[location];
252275
return new Error(
253-
`Invalid ${locationName} \`${propName}\` supplied to ` +
276+
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
254277
`\`${componentName}\`, expected a ReactNode.`
255278
);
256279
}
@@ -260,13 +283,13 @@ function createNodeChecker() {
260283
}
261284

262285
function createShapeTypeChecker(shapeTypes) {
263-
function validate(props, propName, componentName, location) {
286+
function validate(props, propName, componentName, location, propFullName) {
264287
var propValue = props[propName];
265288
var propType = getPropType(propValue);
266289
if (propType !== 'object') {
267290
var locationName = ReactPropTypeLocationNames[location];
268291
return new Error(
269-
`Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` +
292+
`Invalid ${locationName} \`${propFullName}\` of type \`${propType}\` ` +
270293
`supplied to \`${componentName}\`, expected \`object\`.`
271294
);
272295
}
@@ -275,7 +298,13 @@ function createShapeTypeChecker(shapeTypes) {
275298
if (!checker) {
276299
continue;
277300
}
278-
var error = checker(propValue, key, componentName, location);
301+
var error = checker(
302+
propValue,
303+
key,
304+
componentName,
305+
location,
306+
`${propFullName}.${key}`
307+
);
279308
if (error) {
280309
return error;
281310
}

src/classic/types/__tests__/ReactPropTypes-test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ describe('ReactPropTypes', function() {
161161
typeCheckFail(
162162
PropTypes.arrayOf(PropTypes.number),
163163
[1, 2, 'b'],
164-
'Invalid prop `2` of type `string` supplied to `testComponent`, ' +
164+
'Invalid prop `testProp[2]` of type `string` supplied to `testComponent`, ' +
165165
'expected `number`.'
166166
);
167167
});
@@ -173,7 +173,7 @@ describe('ReactPropTypes', function() {
173173
typeCheckFail(
174174
PropTypes.arrayOf(PropTypes.instanceOf(Thing)),
175175
[new Thing(), 'xyz'],
176-
'Invalid prop `1` supplied to `testComponent`, expected instance of `' +
176+
'Invalid prop `testProp[1]` supplied to `testComponent`, expected instance of `' +
177177
name + '`.'
178178
);
179179
});
@@ -458,7 +458,7 @@ describe('ReactPropTypes', function() {
458458
typeCheckFail(
459459
PropTypes.objectOf(PropTypes.number),
460460
{a: 1, b: 2, c: 'b'},
461-
'Invalid prop `c` of type `string` supplied to `testComponent`, ' +
461+
'Invalid prop `testProp.c` of type `string` supplied to `testComponent`, ' +
462462
'expected `number`.'
463463
);
464464
});
@@ -470,7 +470,7 @@ describe('ReactPropTypes', function() {
470470
typeCheckFail(
471471
PropTypes.objectOf(PropTypes.instanceOf(Thing)),
472472
{a: new Thing(), b: 'xyz'},
473-
'Invalid prop `b` supplied to `testComponent`, expected instance of `' +
473+
'Invalid prop `testProp.b` supplied to `testComponent`, expected instance of `' +
474474
name + '`.'
475475
);
476476
});
@@ -668,7 +668,7 @@ describe('ReactPropTypes', function() {
668668
typeCheckFail(
669669
PropTypes.shape({key: PropTypes.number.isRequired}),
670670
{},
671-
'Required prop `key` was not specified in `testComponent`.'
671+
'Required prop `testProp.key` was not specified in `testComponent`.'
672672
);
673673
});
674674

@@ -679,14 +679,14 @@ describe('ReactPropTypes', function() {
679679
secondKey: PropTypes.number.isRequired
680680
}),
681681
{},
682-
'Required prop `key` was not specified in `testComponent`.'
682+
'Required prop `testProp.key` was not specified in `testComponent`.'
683683
);
684684
});
685685

686686
it("should warn for invalid key types", function() {
687687
typeCheckFail(PropTypes.shape({key: PropTypes.number}),
688688
{key: 'abc'},
689-
'Invalid prop `key` of type `string` supplied to `testComponent`, ' +
689+
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
690690
'expected `number`.'
691691
);
692692
});

0 commit comments

Comments
 (0)