Skip to content

Commit

Permalink
chore: Options null value warning (#303)
Browse files Browse the repository at this point in the history
* chore: add latest prettier to devDependencies

* feat: add null is a value warning

* test: add null is a value warning test cases
  • Loading branch information
shezhangzhang authored Jun 29, 2022
1 parent d7fac6a commit 5cb966c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 27 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"gh-pages": "^3.1.0",
"glob": "^7.1.6",
"np": "^7.6.0",
"prettier": "^2.7.1",
"rc-form": "^2.4.0",
"rc-trigger": "^5.0.4",
"react": "^16.0.0",
Expand Down
44 changes: 17 additions & 27 deletions src/Cascader.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import * as React from 'react';
import type { BaseSelectProps, BaseSelectPropsWithoutPrivate, BaseSelectRef } from 'rc-select';
import { BaseSelect } from 'rc-select';
import type { DisplayValueType, Placement } from 'rc-select/lib/BaseSelect';
import useId from 'rc-select/lib/hooks/useId';
import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import type { DisplayValueType, Placement } from 'rc-select/lib/BaseSelect';
import type { BaseSelectRef, BaseSelectPropsWithoutPrivate, BaseSelectProps } from 'rc-select';
import { BaseSelect } from 'rc-select';
import OptionList from './OptionList';
import * as React from 'react';
import CascaderContext from './context';
import { fillFieldNames, toPathKey, toPathKeys, SHOW_PARENT, SHOW_CHILD } from './utils/commonUtil';
import useDisplayValues from './hooks/useDisplayValues';
import useRefFunc from './hooks/useRefFunc';
import useEntities from './hooks/useEntities';
import { formatStrategyValues, toPathOptions } from './utils/treeUtil';
import useMissingValues from './hooks/useMissingValues';
import useRefFunc from './hooks/useRefFunc';
import useSearchConfig from './hooks/useSearchConfig';
import useSearchOptions from './hooks/useSearchOptions';
import warning from 'rc-util/lib/warning';
import useMissingValues from './hooks/useMissingValues';
import OptionList from './OptionList';
import { fillFieldNames, SHOW_CHILD, SHOW_PARENT, toPathKey, toPathKeys } from './utils/commonUtil';
import { formatStrategyValues, toPathOptions } from './utils/treeUtil';
import warningProps, { warningNullOptions } from './utils/warningPropsUtil';

export interface ShowSearchType<OptionType extends BaseOptionType = DefaultOptionType> {
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames) => boolean;
Expand Down Expand Up @@ -134,7 +134,7 @@ export type CascaderProps<OptionType extends BaseOptionType = DefaultOptionType>
| SingleCascaderProps<OptionType>
| MultipleCascaderProps<OptionType>;

type InternalCascaderProps<OptionType extends BaseOptionType = DefaultOptionType> = Omit<
export type InternalCascaderProps<OptionType extends BaseOptionType = DefaultOptionType> = Omit<
SingleCascaderProps<OptionType> | MultipleCascaderProps<OptionType>,
'onChange'
> & {
Expand Down Expand Up @@ -412,22 +412,6 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
};

// ============================ Open ============================
if (process.env.NODE_ENV !== 'production') {
warning(
!onPopupVisibleChange,
'`onPopupVisibleChange` is deprecated. Please use `onDropdownVisibleChange` instead.',
);
warning(popupVisible === undefined, '`popupVisible` is deprecated. Please use `open` instead.');
warning(
popupClassName === undefined,
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
);
warning(
popupPlacement === undefined,
'`popupPlacement` is deprecated. Please use `placement` instead.',
);
}

const mergedOpen = open !== undefined ? open : popupVisible;

const mergedDropdownClassName = dropdownClassName || popupClassName;
Expand All @@ -439,6 +423,12 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
onPopupVisibleChange?.(nextVisible);
};

// ========================== Warning ===========================
if (process.env.NODE_ENV !== 'production') {
warningProps(props);
warningNullOptions(mergedOptions, mergedFieldNames);
}

// ========================== Context ===========================
const cascaderContext = React.useMemo(
() => ({
Expand Down
47 changes: 47 additions & 0 deletions src/utils/warningPropsUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import warning from 'rc-util/lib/warning';
import type { DefaultOptionType, FieldNames, InternalCascaderProps } from '../Cascader';

function warningProps(props: InternalCascaderProps) {
const { onPopupVisibleChange, popupVisible, popupClassName, popupPlacement } = props;

warning(
!onPopupVisibleChange,
'`onPopupVisibleChange` is deprecated. Please use `onDropdownVisibleChange` instead.',
);
warning(popupVisible === undefined, '`popupVisible` is deprecated. Please use `open` instead.');
warning(
popupClassName === undefined,
'`popupClassName` is deprecated. Please use `dropdownClassName` instead.',
);
warning(
popupPlacement === undefined,
'`popupPlacement` is deprecated. Please use `placement` instead.',
);
}

// value in Cascader options should not be null
export function warningNullOptions(options: DefaultOptionType[], fieldNames: FieldNames) {
if (options) {
const recursiveOptions = (optionsList: DefaultOptionType[]) => {
for (let i = 0; i < optionsList.length; i++) {
const option = optionsList[i];

if (option[fieldNames?.value] === null) {
warning(false, '`value` in Cascader options should not be `null`.');
return true;
}

if (
Array.isArray(option[fieldNames?.children]) &&
recursiveOptions(option[fieldNames?.children])
) {
return true;
}
}
};

recursiveOptions(options);
}
}

export default warningProps;
32 changes: 32 additions & 0 deletions tests/fieldNames.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,36 @@ describe('Cascader.FieldNames', () => {

expect(wrapper.find('.rc-cascader-menu-item').last().text()).toEqual('Not Found');
});

it('`null` is a value in fieldNames options should throw a warning', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => null);
mount(
<Cascader
fieldNames={fieldNames}
options={[
{
customTitle: '四川',
customValue: 'sc',
customChildren: [
{
customTitle: '成都',
customValue: 'cd',
customChildren: [
{
customTitle: '天府新区',
customValue: null,
},
],
},
],
},
]}
/>,
);

expect(errorSpy).toHaveBeenCalledWith(
'Warning: `value` in Cascader options should not be `null`.',
);
errorSpy.mockReset();
});
});
31 changes: 31 additions & 0 deletions tests/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -946,4 +946,35 @@ describe('Cascader.Basic', () => {
it('not crash when value type is not array', () => {
mount(<Cascader value={'bamboo' as any} />);
});

it('`null` is a value in Cascader options should throw a warning', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => null);
mount(
<Cascader
options={[
{
label: '四川',
value: 'sc',
children: [
{
label: '成都',
value: 'cd',
children: [
{
label: '天府新区',
value: null,
},
],
},
],
},
]}
/>,
);

expect(errorSpy).toHaveBeenCalledWith(
'Warning: `value` in Cascader options should not be `null`.',
);
errorSpy.mockReset();
});
});

1 comment on commit 5cb966c

@vercel
Copy link

@vercel vercel bot commented on 5cb966c Jun 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

cascader – ./

cascader-git-master-react-component.vercel.app
cascader-react-component.vercel.app

Please sign in to comment.