diff --git a/common/changes/office-ui-fabric-react/kysedate-combobox-perf_2018-05-04-22-36.json b/common/changes/office-ui-fabric-react/kysedate-combobox-perf_2018-05-04-22-36.json new file mode 100644 index 0000000000000..99633a41dab77 --- /dev/null +++ b/common/changes/office-ui-fabric-react/kysedate-combobox-perf_2018-05-04-22-36.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Even if a ComboBoxOptionWrapper component doesn't update (shouldComponentUpdate returns false), children functions are still being executed unnecessarily, which can cause performance issues. This can be avoided by passing in a reference to a function that returns the children elements, instead of returning the elements themselves.", + "type": "patch" + } + ], + "packageName": "office-ui-fabric-react", + "email": "kysedate@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 977b1261608e9..92e3337a030e3 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -83,6 +83,11 @@ enum HoverStatus { interface IComboBoxOptionWrapperProps extends IComboBoxOption { // True if the option is currently selected isSelected: boolean; + + // A function that returns the children of the OptionWrapper. We pass this in as a function to ensure that + // children methods don't get called unnecessarily if the component doesn't need to be updated. This leads + // to a significant performance increase in ComboBoxes with many options and/or complex onRenderOption functions + render: () => JSX.Element; } // Internal class that is used to wrap all ComboBox options @@ -90,12 +95,12 @@ interface IComboBoxOptionWrapperProps extends IComboBoxOption { // so we don't rerender every option every time render is executed class ComboBoxOptionWrapper extends React.Component { public render(): React.ReactNode { - return this.props.children; + return this.props.render(); } public shouldComponentUpdate(newProps: IComboBoxOptionWrapperProps): boolean { - // The children will always be different, so we ignore that prop - return !shallowCompare({ ...this.props, children: undefined }, { ...newProps, children: undefined }); + // The render function will always be different, so we ignore that prop + return !shallowCompare({ ...this.props, render: undefined }, { ...newProps, render: undefined }); } } @@ -1053,26 +1058,14 @@ export class ComboBox extends BaseComponent { const { onRenderOption = this._onRenderOptionContent } = this.props; const id = this._id; const isSelected: boolean = this._isOptionSelected(item.index); - const optionStyles = this._getCurrentOptionStyles(item); - const wrapperProps = { - key: item.key, - index: item.index, - styles: optionStyles, - disabled: item.disabled, - isSelected: isSelected, - text: item.text, - }; - - return ( - !this.props.multiSelect ? ( - + const getOptionComponent = () => { + return ( + !this.props.multiSelect ? ( { } - - ) : ( - + ) : ( { > { onRenderOption(item, this._onRenderOptionContent) } - - ) + ) + ); + }; + + return ( + ); }