diff --git a/src/PropTypes.js b/src/PropTypes.js new file mode 100644 index 000000000..9317e2c81 --- /dev/null +++ b/src/PropTypes.js @@ -0,0 +1,66 @@ +import { PropTypes } from 'react'; + +function valueType(props, propName, componentName) { + const labelInValueShape = PropTypes.shape({ + key: PropTypes.string.isRequired, + label: PropTypes.string, + }); + if (props.labelInValue) { + const validate = PropTypes.oneOfType([ + PropTypes.arrayOf(labelInValueShape), + labelInValueShape, + ]); + const error = validate(...arguments); + if (error) { + return new Error( + `Invalid prop \`${propName}\` supplied to \`${componentName}\`, ` + + `when you set \`labelInValue\` to \`true\`, \`${propName}\` should in ` + + `shape of \`{ key: string, label?: string }\`.` + ); + } + } else if (props.multiple && props[propName] === '') { + return new Error( + `Invalid prop \`${propName}\` of type \`string\` supplied to \`${componentName}\`, ` + + `expected \`array\` when \`multiple\` is \`true\`.` + ); + } else { + const validate = PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.string, + ]); + return validate(...arguments); + } +} + +export const SelectPropTypes = { + defaultActiveFirstOption: PropTypes.bool, + multiple: PropTypes.bool, + filterOption: PropTypes.any, + children: PropTypes.any, + showSearch: PropTypes.bool, + disabled: PropTypes.bool, + allowClear: PropTypes.bool, + showArrow: PropTypes.bool, + tags: PropTypes.bool, + prefixCls: PropTypes.string, + className: PropTypes.string, + transitionName: PropTypes.string, + optionLabelProp: PropTypes.string, + optionFilterProp: PropTypes.string, + animation: PropTypes.string, + choiceTransitionName: PropTypes.string, + onChange: PropTypes.func, + onBlur: PropTypes.func, + onFocus: PropTypes.func, + onSelect: PropTypes.func, + onSearch: PropTypes.func, + placeholder: PropTypes.any, + onDeselect: PropTypes.func, + labelInValue: PropTypes.bool, + value: valueType, + defaultValue: valueType, + dropdownStyle: PropTypes.object, + maxTagTextLength: PropTypes.number, + tokenSeparators: PropTypes.arrayOf(PropTypes.string), + getInputElement: PropTypes.func, +}; diff --git a/src/Select.jsx b/src/Select.jsx index 2e936b481..d7a8eba78 100644 --- a/src/Select.jsx +++ b/src/Select.jsx @@ -1,4 +1,4 @@ -import React, { PropTypes } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom'; import KeyCode from 'rc-util/lib/KeyCode'; import classnames from 'classnames'; @@ -15,6 +15,7 @@ import { } from './util'; import SelectTrigger from './SelectTrigger'; import FilterMixin from './FilterMixin'; +import { SelectPropTypes } from './PropTypes'; function noop() { } @@ -27,57 +28,8 @@ function saveRef(name, component) { this[name] = component; } -let valueObjectShape; - -if (PropTypes) { - valueObjectShape = PropTypes.oneOfType([ - PropTypes.string, - PropTypes.shape({ - key: PropTypes.string, - label: PropTypes.node, - }), - ]); -} - const Select = React.createClass({ - propTypes: { - defaultActiveFirstOption: PropTypes.bool, - multiple: PropTypes.bool, - filterOption: PropTypes.any, - children: PropTypes.any, - showSearch: PropTypes.bool, - disabled: PropTypes.bool, - allowClear: PropTypes.bool, - showArrow: PropTypes.bool, - tags: PropTypes.bool, - prefixCls: PropTypes.string, - className: PropTypes.string, - transitionName: PropTypes.string, - optionLabelProp: PropTypes.string, - optionFilterProp: PropTypes.string, - animation: PropTypes.string, - choiceTransitionName: PropTypes.string, - onChange: PropTypes.func, - onBlur: PropTypes.func, - onFocus: PropTypes.func, - onSelect: PropTypes.func, - onSearch: PropTypes.func, - placeholder: PropTypes.any, - onDeselect: PropTypes.func, - labelInValue: PropTypes.bool, - value: PropTypes.oneOfType([ - valueObjectShape, - PropTypes.arrayOf(valueObjectShape), - ]), - defaultValue: PropTypes.oneOfType([ - valueObjectShape, - PropTypes.arrayOf(valueObjectShape), - ]), - dropdownStyle: PropTypes.object, - maxTagTextLength: PropTypes.number, - tokenSeparators: PropTypes.arrayOf(PropTypes.string), - getInputElement: PropTypes.func, - }, + propTypes: SelectPropTypes, mixins: [FilterMixin], @@ -91,7 +43,6 @@ const Select = React.createClass({ showSearch: true, allowClear: false, placeholder: '', - defaultValue: [], onChange: noop, onFocus: noop, onBlur: noop, diff --git a/tests/Select.spec.js b/tests/Select.spec.js index f4a7b9078..7580f5963 100644 --- a/tests/Select.spec.js +++ b/tests/Select.spec.js @@ -441,4 +441,38 @@ describe('Select', () => { dropdownWrapper.find('MenuItem').first().simulate('click'); expect(wrapper.state().inputValue).toBe('1'); }); + + describe('propTypes', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + beforeEach(() => { + spy.mockReset(); + }); + + afterAll(() => { + spy.mockRestore(); + }); + + it('warns on invalid value when labelInValue', () => { + expect(() => { + mount( + + ); + expect(spy.mock.calls[0][0]).toMatch( + 'Invalid prop `value` of type `string` supplied to `Select`, ' + + 'expected `array` when `multiple` is `true`' + ); + }); + }); });