React: refactor away from unsafe lifecycle methods (#21291)

* refactor aliasBy and PopoverCtrl components

* refactor Aggregations component

* refactor MetricSelect component

* refactor UserProvider

* popoverCtr: remove redundant logic

* simplified the MetricsSelect a bit.

* skipping testing of internal component logic.

* changed to componentWillMount.

* changed elapsed time to a functional component.

* rewrote the tests.

* fixed missing test title.

* fixed a tiny issue with elapsed time.

* rename of field.

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
This commit is contained in:
Boyko
2020-05-18 19:03:29 +03:00
committed by GitHub
parent 44fae66bc0
commit 1e74037eae
9 changed files with 242 additions and 278 deletions

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { shallow } from 'enzyme';
import { MetricSelect } from './MetricSelect';
import { LegacyForms } from '@grafana/ui';
const { Select } = LegacyForms;
describe('MetricSelect', () => {
describe('When receive props', () => {
it('should pass correct set of props to Select component', () => {
const props: any = {
placeholder: 'Select Reducer',
className: 'width-15',
options: [],
variables: [],
};
const wrapper = shallow(<MetricSelect {...props} />);
expect(wrapper.find(Select).props()).toMatchObject({
className: 'width-15',
isMulti: false,
isClearable: false,
backspaceRemovesValue: false,
isSearchable: true,
maxMenuHeight: 500,
placeholder: 'Select Reducer',
});
});
it('should pass callbacks correctly to the Select component', () => {
const spyOnChange = jest.fn();
const props: any = {
onChange: spyOnChange,
options: [],
variables: [],
};
const wrapper = shallow(<MetricSelect {...props} />);
wrapper
.find(Select)
.props()
.onChange({ value: 'foo' });
expect(
wrapper
.find(Select)
.props()
.noOptionsMessage()
).toEqual('No options found');
expect(spyOnChange).toHaveBeenCalledWith('foo');
});
});
});

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo, useCallback, FC } from 'react';
import _ from 'lodash';
import { LegacyForms } from '@grafana/ui';
@@ -16,75 +16,51 @@ export interface Props {
variables?: Variable[];
}
interface State {
options: Array<SelectableValue<string>>;
}
export const MetricSelect: FC<Props> = props => {
const { value, placeholder, className, isSearchable, onChange } = props;
const options = useSelectOptions(props);
const selected = useSelectedOption(options, value);
const onChangeValue = useCallback((selectable: SelectableValue<string>) => onChange(selectable.value), [onChange]);
export class MetricSelect extends React.Component<Props, State> {
static defaultProps: Partial<Props> = {
variables: [],
options: [],
isSearchable: true,
};
return (
<Select
className={className}
isMulti={false}
isClearable={false}
backspaceRemovesValue={false}
onChange={onChangeValue}
options={options}
isSearchable={isSearchable}
maxMenuHeight={500}
placeholder={placeholder}
noOptionsMessage={() => 'No options found'}
value={selected}
/>
);
};
constructor(props: Props) {
super(props);
this.state = { options: [] };
}
componentDidMount() {
this.setState({ options: this.buildOptions(this.props) });
}
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.options.length > 0 || nextProps.variables?.length) {
this.setState({ options: this.buildOptions(nextProps) });
const useSelectOptions = ({ variables = [], options }: Props): Array<SelectableValue<string>> => {
return useMemo(() => {
if (!Array.isArray(variables) || variables.length === 0) {
return options;
}
}
shouldComponentUpdate(nextProps: Props) {
const nextOptions = this.buildOptions(nextProps);
return nextProps.value !== this.props.value || !_.isEqual(nextOptions, this.state.options);
}
return [
{
label: 'Template Variables',
options: variables.map(({ name }) => ({
label: `$${name}`,
value: `$${name}`,
})),
},
...options,
];
}, [variables, options]);
};
buildOptions({ variables = [], options }: Props) {
return variables.length > 0 ? [this.getVariablesGroup(), ...options] : options;
}
getVariablesGroup() {
return {
label: 'Template Variables',
options: this.props.variables?.map(v => ({
label: `$${v.name}`,
value: `$${v.name}`,
})),
};
}
getSelectedOption() {
const { options } = this.state;
const useSelectedOption = (options: Array<SelectableValue<string>>, value: string): SelectableValue<string> => {
return useMemo(() => {
const allOptions = options.every(o => o.options) ? _.flatten(options.map(o => o.options)) : options;
return allOptions.find(option => option.value === this.props.value);
}
render() {
const { placeholder, className, isSearchable, onChange } = this.props;
const { options } = this.state;
const selectedOption = this.getSelectedOption();
return (
<Select
className={className}
isMulti={false}
isClearable={false}
backspaceRemovesValue={false}
onChange={item => onChange(item.value ?? '')}
options={options}
isSearchable={isSearchable}
maxMenuHeight={500}
placeholder={placeholder}
noOptionsMessage={() => 'No options found'}
value={selectedOption}
/>
);
}
}
return allOptions.find(option => option.value === value);
}, [options, value]);
};