<commit_before>// @flow

import React from 'react';
import Select from 'react-select'
import {Dictionary, createDictionary} from "../../types/Generics.jsx";
import type {SelectOption} from "../../types/SelectOption.jsx";
import _ from "lodash";

type SelectAction = {|
  name: string,
  action: "clear" | "select-option" | "deselect-option" | "remove-value" | "pop-value" | "set-value" | "create-option"
|};

type SelectorFlags = {|
  isSearchable: boolean,
  isClearable: boolean,
  isMultiSelect: boolean
|};

const defaultFlags: SelectorFlags = {
  isSearchable: false,
  isClearable: true,
  isMultiSelect: false
};

type Props<T> = {|
  id: string,
  options: $ReadOnlyArray<T>,
  selected: T,
  placeholder: string | React$Node,
  noOptionsMessage: string | () => string | React$Node,
  labelGenerator: (T) => string,
  valueStringGenerator: (T) => string,
  onSelection: (T) => void,
  onInputChange: ?(string) => void
|} & SelectorFlags;

type State<T> = {|
  labelGenerator: (T) => string,
  selected: SelectOption,
  selectOptions: $ReadOnlyArray<SelectOption>,
  noOptionsMessage: () => string | React$Node,
  optionIndex: Dictionary<T>,
  isCleared: boolean
|};

class Selector<T> extends React.PureComponent<Props<T>, State<T>> {
  constructor(props: Props<T>): void {
    super();
    
    this.state = this.updateOptions(props, {});
  }
  
  findOption<T>(selectOptions: $ReadOnlyArray<SelectOption>, option: string) {
    return selectOptions.find((selectOption: SelectOption) => selectOption.label === option);
  }
  
  updateOptions(props: Props, state: State): State {
    state.labelGenerator = props.labelGenerator || _.toString;
    if(props.noOptionsMessage) {
      state.noOptionsMessage = _.isString(props.noOptionsMessage) ? () => props.noOptionsMessage : props.noOptionsMessage;
    }
    if(props.options) {
      state.optionIndex = createDictionary(props.options, state.labelGenerator);
      state.selectOptions = props.options.map(key => ({
        value: (props.valueStringGenerator || state.labelGenerator)(key),
        label: state.labelGenerator(key)
      }));
    }
    if(props.selected) {
      if(props.options) {
        state.selected = props.selected && this.findOption(state.selectOptions, state.labelGenerator(props.selected));
      } else if(!state.isCleared) {
        state.selectOptions = [{
          value: (props.valueStringGenerator || state.labelGenerator)(props.selected),
          label: state.labelGenerator(props.selected)
        }];
        state.selected = state.selectOptions[0];
      }
    }
    return state;
  }
  

  componentWillReceiveProps(nextProps: Props): void {
    this.setState(this.updateOptions(nextProps, this.state), function() {
      this.forceUpdate();
    });
  }

  handleSelection(selection: SelectOption, selectionAction: SelectAction) {
    if(selectionAction.action === "clear") {
      this.setState((previousState: State) => {
        return {
          ...previousState,
          isCleared: true,
          selected: null,
          selectOptions: []
        };
      });
      this.forceUpdate();
    }
    this.props.onSelection(selection ? this.state.optionIndex[selection.label] : null);
  }
  
  onInputChange(inputValue: string): void {
    this.props.onInputChange && this.props.onInputChange(inputValue);
  }
  
  render(): React$Node {<commit_msg>Bug Fix: Use getDerivedStateFromProps to fix bug where learing Selector placeholder takes two clicks<commit_after>// @flow

import React from 'react';
import Select from 'react-select'
import {Dictionary, createDictionary} from "../../types/Generics.jsx";
import type {SelectOption} from "../../types/SelectOption.jsx";
import _ from "lodash";

type SelectAction = {|
  name: string,
  action: "clear" | "select-option" | "deselect-option" | "remove-value" | "pop-value" | "set-value" | "create-option"
|};

type SelectorFlags = {|
  isSearchable: boolean,
  isClearable: boolean,
  isMultiSelect: boolean
|};

const defaultFlags: SelectorFlags = {
  isSearchable: false,
  isClearable: true,
  isMultiSelect: false
};

type Props<T> = {|
  id: string,
  options: $ReadOnlyArray<T>,
  selected: T,
  placeholder: string | React$Node,
  noOptionsMessage: string | () => string | React$Node,
  labelGenerator: (T) => string,
  valueStringGenerator: (T) => string,
  onSelection: (T) => void,
  onInputChange: ?(string) => void
|} & SelectorFlags;

type State<T> = {|
  labelGenerator: (T) => string,
  selected: SelectOption,
  selectOptions: $ReadOnlyArray<SelectOption>,
  noOptionsMessage: () => string | React$Node,
  optionIndex: Dictionary<T>,
  isCleared: boolean
|};

class Selector<T> extends React.PureComponent<Props<T>, State<T>> {
  constructor(props: Props<T>): void {
    super();
    
    this.state = Selector.updateOptions(props, {});
  }
  
  static findOption<T>(selectOptions: $ReadOnlyArray<SelectOption>, option: string) {
    return selectOptions.find((selectOption: SelectOption) => selectOption.label === option);
  }
  
  static updateOptions(props: Props, state: State): State {
    state.labelGenerator = props.labelGenerator || _.toString;
    if(props.noOptionsMessage) {
      state.noOptionsMessage = _.isString(props.noOptionsMessage) ? () => props.noOptionsMessage : props.noOptionsMessage;
    }
    if(props.options) {
      state.optionIndex = createDictionary(props.options, state.labelGenerator);
      state.selectOptions = props.options.map(key => ({
        value: (props.valueStringGenerator || state.labelGenerator)(key),
        label: state.labelGenerator(key)
      }));
    }
    if(props.selected) {
      if(props.options) {
        state.selected = props.selected && Selector.findOption(state.selectOptions, state.labelGenerator(props.selected));
      } else if(!state.isCleared) {
        state.selectOptions = [{
          value: (props.valueStringGenerator || state.labelGenerator)(props.selected),
          label: state.labelGenerator(props.selected)
        }];
        state.selected = state.selectOptions[0];
      }
    }
    return state;
  }
  

  componentWillReceiveProps(nextProps: Props): void {
    this.setState(Selector.updateOptions(nextProps, this.state), function() {
      this.forceUpdate();
    });
  }

  handleSelection(selection: SelectOption, selectionAction: SelectAction) {
    if(selectionAction.action === "clear") {
      this.setState((previousState: State) => {
        return {
          ...previousState,
          isCleared: true,
          selected: null,
          selectOptions: []
        };
      });
      this.forceUpdate();
    }
    this.props.onSelection(selection ? this.state.optionIndex[selection.label] : null);
  }
  
  static getDerivedStateFromProps(props, state) {
    // Fix for Bug where user has to click twice in order to remove placeholder selector item
    return Selector.updateOptions(props, state);
  }
  
  onInputChange(inputValue: string): void {
    this.props.onInputChange && this.props.onInputChange(inputValue);
  }
  
  render(): React$Node {
{'fraction_non_alphanumeric': 0.13886910696481983, 'fraction_numerical': 0.0002848597065945022, 'mean_word_length': 3.6106369008535784, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 49, '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': '3130314', 'n_tokens_mistral': 2110, 'n_tokens_neox': 2105, 'n_words': 419}