Skip to content
This repository was archived by the owner on Oct 19, 2021. It is now read-only.

Commit 7edc0d9

Browse files
asudohjoshblack
authored andcommitted
fix(Icon): add prop type check for either icon or name prop exists (#1132)
* fix(Icon): add prop type check for either icon or name prop exists * fix(Icon): add better possible cause in the invariant() * chore(Icon): move the file for prop type check * chore(Icon): make prop type check API simpler
1 parent b98b370 commit 7edc0d9

File tree

4 files changed

+68
-31
lines changed

4 files changed

+68
-31
lines changed

src/components/DataTable/TableToolbarAction.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import cx from 'classnames';
22
import PropTypes from 'prop-types';
33
import React from 'react';
44
import Icon from '../Icon';
5+
import isRequiredOneOf from '../../prop-types/isRequiredOneOf';
56

67
const TableToolbarAction = ({
78
className,
@@ -27,20 +28,22 @@ TableToolbarAction.propTypes = {
2728
children: PropTypes.node,
2829
className: PropTypes.string,
2930

30-
/**
31-
* Specify the icon for the toolbar action
32-
*/
33-
icon: PropTypes.shape({
34-
width: PropTypes.string,
35-
height: PropTypes.string,
36-
viewBox: PropTypes.string.isRequired,
37-
svgData: PropTypes.object.isRequired,
38-
}).isRequired,
31+
...isRequiredOneOf({
32+
/**
33+
* Specify the icon for the toolbar action
34+
*/
35+
icon: PropTypes.shape({
36+
width: PropTypes.string,
37+
height: PropTypes.string,
38+
viewBox: PropTypes.string.isRequired,
39+
svgData: PropTypes.object.isRequired,
40+
}),
3941

40-
/**
41-
* Specify the name of the icon for the toolbar action
42-
*/
43-
iconName: PropTypes.string,
42+
/**
43+
* Specify the name of the icon for the toolbar action
44+
*/
45+
iconName: PropTypes.string,
46+
}),
4447

4548
/**
4649
* Specify the description of the icon for the toolbar action

src/components/Icon/Icon-test.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ describe('Icon', () => {
4545
it('should recieve style props', () => {
4646
expect(wrapper.props().style).toEqual({ transition: '2s' });
4747
});
48-
49-
it('should not crash when there is no name prop', () => {
50-
mount(<Icon />);
51-
});
5248
});
5349

5450
describe('Supports legacy icon', () => {

src/components/Icon/Icon.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import invariant from 'invariant';
12
import PropTypes from 'prop-types';
23
import React from 'react';
34
import icons from 'carbon-icons';
5+
import isRequiredOneOf from '../../prop-types/isRequiredOneOf';
46

57
/**
68
* The icons list object from `carbon-icons`.
@@ -88,7 +90,15 @@ export function svgShapes(svgData) {
8890
}
8991

9092
export function isPrefixed(name) {
91-
return !!name && name.split('--')[0] === 'icon';
93+
if (__DEV__) {
94+
invariant(
95+
typeof name === 'string',
96+
'[Icon] icon name is missing. You likely forgot to specify the icon, ' +
97+
'or are using older (pre-`7.x`) version of `carbon-icons` library. ' +
98+
'To specify the icon, use either `icon` (data) or `name` (icon name) properties.'
99+
);
100+
}
101+
return name && name.split('--')[0] === 'icon';
92102
}
93103

94104
const Icon = ({
@@ -155,21 +165,23 @@ Icon.propTypes = {
155165
*/
156166
height: PropTypes.string,
157167

158-
/**
159-
* The icon data.
160-
*/
161-
icon: PropTypes.shape({
162-
width: PropTypes.string,
163-
height: PropTypes.string,
164-
viewBox: PropTypes.string.isRequired,
165-
svgData: PropTypes.object.isRequired,
168+
...isRequiredOneOf({
169+
/**
170+
* The icon data.
171+
*/
172+
icon: PropTypes.shape({
173+
width: PropTypes.string,
174+
height: PropTypes.string,
175+
viewBox: PropTypes.string.isRequired,
176+
svgData: PropTypes.object.isRequired,
177+
}),
178+
179+
/**
180+
* The name in the sprite.
181+
*/
182+
name: PropTypes.string,
166183
}),
167184

168-
/**
169-
* The name in the sprite.
170-
*/
171-
name: PropTypes.string,
172-
173185
/**
174186
* The `role` attribute.
175187
*/

src/prop-types/isRequiredOneOf.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @param {Object<string, Function>} propTypes The list of type checkers, keyed by prop names.
3+
* @returns {Object<string, Function>}
4+
* The new prop type checkers that checks if one of the given props exist,
5+
* in addition to the original type checkings.
6+
*/
7+
export default function isRequiredOneOf(propTypes) {
8+
const names = Object.keys(propTypes);
9+
const checker = propType => (props, propName, componentName, ...rest) => {
10+
if (__DEV__ && names.every(name => !props.hasOwnProperty(name))) {
11+
return new Error(
12+
`${componentName} requires one of the following props: ${names.join(
13+
', '
14+
)}`
15+
);
16+
}
17+
return propType(props, propName, componentName, ...rest);
18+
};
19+
return names.reduce(
20+
(o, name) => ({
21+
...o,
22+
[name]: checker(propTypes[name]),
23+
}),
24+
{}
25+
);
26+
}

0 commit comments

Comments
 (0)