<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { Icon } from '../../../Icon';
import { Button } from '../../../Button';
import { Listbox, Pill } from '../../../Pill';
import { OverflowContainer } from './OverflowContainer';

const isPropChanged = prop => (a, b) => a[prop] !== b[prop];
const isLengthChanged = isPropChanged('length');
const isIdChanged = isPropChanged('id');
const isOpenChanged = isPropChanged('isOpen');

class ComboboxGroupedListbox extends Component {
  static propTypes = {
    isExpanded: PropTypes.bool,
    isOpen: PropTypes.bool.isRequired,
    label: PropTypes.string.isRequired,
    makeSelectHandler: PropTypes.func.isRequired,
    onExpand: PropTypes.func.isRequired,
    overflowLabel: PropTypes.oneOfType([
      PropTypes.func,
      PropTypes.string,
    ]),
    selectedItems: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.string.isRequired,
      label: PropTypes.string.isRequired,
    })).isRequired,
    showItemsWhenOpen: PropTypes.bool,
  }

  static defaultProps = {
    isExpanded: false,
    overflowLabel: cnt => `+${cnt} more`,
    showItemsWhenOpen: false,
  }

  constructor(props) {
    super(props);
    this.state = { forcedUpdateTimestamp: null };
    this.listboxRef = React.createRef();
  }

  componentDidUpdate(prevProps) {
    const { selectedItems, isExpanded } = this.props;
    const { selectedItems: prevSelectedItems } = prevProps;

    // Opimization: DOM access is expensive here. Avoid for small collections if possible
    if (isExpanded) return;

    const isChanged = isOpenChanged(this.props, prevProps)
      || isLengthChanged(selectedItems, prevSelectedItems)
      || selectedItems.length > 100
      || selectedItems.some((x, i) => isIdChanged(x, prevSelectedItems[i]));

    if (isChanged) {
      // eslint-disable-next-line react/no-did-update-set-state
      this.setState({ forcedUpdateTimestamp: Date.now() });
    }
  }

  render() {
    const {
      hideWhenEmpty,
      isExpanded,
      isOpen,
      label,
      makeSelectHandler,
      onExpand,
      overflowLabel,
      renderPill,
      selectedItems,
      showItemsWhenOpen,
    } = this.props;

    const { forcedUpdateTimestamp } = this.state;
    const sldsClasses = [
      'slds-listbox_selection-group',
      { 'slds-is-expanded': isExpanded },<commit_msg>Fix infinite loop for large item collections in ComboboxGroupedListbox (#431)<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { Icon } from '../../../Icon';
import { Button } from '../../../Button';
import { Listbox, Pill } from '../../../Pill';
import { OverflowContainer } from './OverflowContainer';

const isPropChanged = prop => (a, b) => a[prop] !== b[prop];
const isLengthChanged = isPropChanged('length');
const isIdChanged = isPropChanged('id');
const isOpenChanged = isPropChanged('isOpen');

class ComboboxGroupedListbox extends Component {
  static propTypes = {
    isExpanded: PropTypes.bool,
    isOpen: PropTypes.bool.isRequired,
    label: PropTypes.string.isRequired,
    makeSelectHandler: PropTypes.func.isRequired,
    onExpand: PropTypes.func.isRequired,
    overflowLabel: PropTypes.oneOfType([
      PropTypes.func,
      PropTypes.string,
    ]),
    selectedItems: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.string.isRequired,
      label: PropTypes.string.isRequired,
    })).isRequired,
    showItemsWhenOpen: PropTypes.bool,
  }

  static defaultProps = {
    isExpanded: false,
    overflowLabel: cnt => `+${cnt} more`,
    showItemsWhenOpen: false,
  }

  constructor(props) {
    super(props);
    this.state = { forcedUpdateTimestamp: null };
    this.listboxRef = React.createRef();
  }

  componentDidUpdate(prevProps) {
    const { selectedItems, isExpanded } = this.props;
    const { selectedItems: prevSelectedItems } = prevProps;

    if (isExpanded) return;

    if (isOpenChanged(this.props, prevProps) || isLengthChanged(selectedItems, prevSelectedItems)) {
      this.forceRecalculation();
      return;
    }

    // Optimization: Lists longer than 30 items will have an overflow present
    // We can skip a detailed check and assume that only a length change should trigger a recalculation
    if (selectedItems.length < 30 && selectedItems.some((item, i) => isIdChanged(item, prevSelectedItems[i]))) {
      this.forceRecalculation();
    }
  }

  forceRecalculation() {
    // eslint-disable-next-line react/no-did-update-set-state
    this.setState({ forcedUpdateTimestamp: Date.now() });
  }

  render() {
    const {
      hideWhenEmpty,
      isExpanded,
      isOpen,
      label,
      makeSelectHandler,
      onExpand,
      overflowLabel,
      renderPill,
      selectedItems,
      showItemsWhenOpen,
    } = this.props;

    const { forcedUpdateTimestamp } = this.state;
    const sldsClasses = [
      'slds-listbox_selection-group',
      { 'slds-is-expanded': isExpanded },
{'fraction_non_alphanumeric': 0.12509944311853619, 'fraction_numerical': 0.001988862370723946, 'mean_word_length': 3.3465859982713915, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7487787', 'n_tokens_mistral': 1499, 'n_tokens_neox': 1487, 'n_words': 339}