diff --git a/src/Autosuggest.js b/src/Autosuggest.js index bc010aee..40c2183f 100644 --- a/src/Autosuggest.js +++ b/src/Autosuggest.js @@ -9,6 +9,51 @@ const defaultShouldRenderSuggestions = (value) => value.trim().length > 0; const defaultRenderSuggestionsContainer = ({ containerProps, children }) => (
{children}
); +const updateHighlightedSuggestionAux = ( + state, + props, + sectionIndex, + suggestionIndex, + prevValue +) => { + let { valueBeforeUpDown } = state; + const { getSectionSuggestions, suggestions, multiSection } = props; + + if (suggestionIndex === null) { + valueBeforeUpDown = null; + } else if (valueBeforeUpDown === null && typeof prevValue !== 'undefined') { + valueBeforeUpDown = prevValue; + } + + return { + highlightedSectionIndex: multiSection ? sectionIndex : null, + highlightedSuggestionIndex: suggestionIndex, + highlightedSuggestion: + suggestionIndex === null + ? null + : multiSection + ? getSectionSuggestions(suggestions[sectionIndex])[suggestionIndex] + : suggestions[suggestionIndex], + valueBeforeUpDown, + }; +}; +const willRenderSuggestionsAux = (props) => { + const { suggestions, inputProps, shouldRenderSuggestions } = props; + const { value } = inputProps; + + return suggestions.length > 0 && shouldRenderSuggestions(value); +}; + +const resetHighlightedSuggestionAux = (state, shouldResetValueBeforeUpDown) => { + const { valueBeforeUpDown } = state; + + return { + highlightedSectionIndex: null, + highlightedSuggestionIndex: null, + highlightedSuggestion: null, + valueBeforeUpDown: shouldResetValueBeforeUpDown ? null : valueBeforeUpDown, + }; +}; export default class Autosuggest extends Component { static propTypes = { @@ -95,52 +140,75 @@ export default class Autosuggest extends Component { id: '1', }; - constructor({ alwaysRenderSuggestions }) { + constructor(props) { super(); this.state = { isFocused: false, - isCollapsed: !alwaysRenderSuggestions, + isCollapsed: !props.alwaysRenderSuggestions, highlightedSectionIndex: null, highlightedSuggestionIndex: null, highlightedSuggestion: null, valueBeforeUpDown: null, + prevSuggestions: props.suggestions, + justPressedUpDown: false, + justMouseEntered: false, + justSelectedSuggestion: false, }; - this.justPressedUpDown = false; - this.justMouseEntered = false; - this.pressedSuggestion = null; } - componentDidMount() { - document.addEventListener('mousedown', this.onDocumentMouseDown); - document.addEventListener('mouseup', this.onDocumentMouseUp); - - this.input = this.autowhatever.input; - this.suggestionsContainer = this.autowhatever.itemsContainer; - } + static getDerivedStateFromProps(nextProps, prevState) { + const { + justPressedUpDown, + justMouseEntered, + justSelectedSuggestion, + prevSuggestions, + isCollapsed, + } = prevState; + const { suggestions, highlightFirstSuggestion } = nextProps; + const derivedState = { + justMouseEntered: false, + justPressedUpDown: false, + prevSuggestions: suggestions, + }; - // eslint-disable-next-line camelcase, react/sort-comp - UNSAFE_componentWillReceiveProps(nextProps) { - if (shallowEqualArrays(nextProps.suggestions, this.props.suggestions)) { + if (shallowEqualArrays(suggestions, prevSuggestions)) { if ( - nextProps.highlightFirstSuggestion && - nextProps.suggestions.length > 0 && - this.justPressedUpDown === false && - this.justMouseEntered === false + suggestions.length > 0 && + highlightFirstSuggestion && + justMouseEntered === false && + justPressedUpDown === false ) { - this.highlightFirstSuggestion(); + return { + ...derivedState, + ...updateHighlightedSuggestionAux(prevState, nextProps, 0, 0), + }; } } else { - if (this.willRenderSuggestions(nextProps)) { - if (this.state.isCollapsed && !this.justSelectedSuggestion) { - this.revealSuggestions(); - } + if ( + willRenderSuggestionsAux(nextProps) && + isCollapsed && + !justSelectedSuggestion + ) { + return { ...derivedState, ...{ isCollapsed: false } }; } else { - this.resetHighlightedSuggestion(); + return { + ...derivedState, + ...resetHighlightedSuggestionAux(prevState, null), + }; } } + return null; + } + + componentDidMount() { + document.addEventListener('mousedown', this.onDocumentMouseDown); + document.addEventListener('mouseup', this.onDocumentMouseUp); + + this.input = this.autowhatever.input; + this.suggestionsContainer = this.autowhatever.itemsContainer; } componentDidUpdate(prevProps, prevState) { @@ -178,41 +246,19 @@ export default class Autosuggest extends Component { updateHighlightedSuggestion(sectionIndex, suggestionIndex, prevValue) { this.setState((state) => { - let { valueBeforeUpDown } = state; - - if (suggestionIndex === null) { - valueBeforeUpDown = null; - } else if ( - valueBeforeUpDown === null && - typeof prevValue !== 'undefined' - ) { - valueBeforeUpDown = prevValue; - } - - return { - highlightedSectionIndex: sectionIndex, - highlightedSuggestionIndex: suggestionIndex, - highlightedSuggestion: - suggestionIndex === null - ? null - : this.getSuggestion(sectionIndex, suggestionIndex), - valueBeforeUpDown, - }; + return updateHighlightedSuggestionAux( + state, + this.props, + sectionIndex, + suggestionIndex, + prevValue + ); }); } resetHighlightedSuggestion(shouldResetValueBeforeUpDown = true) { this.setState((state) => { - const { valueBeforeUpDown } = state; - - return { - highlightedSectionIndex: null, - highlightedSuggestionIndex: null, - highlightedSuggestion: null, - valueBeforeUpDown: shouldResetValueBeforeUpDown - ? null - : valueBeforeUpDown, - }; + return resetHighlightedSuggestionAux(state, shouldResetValueBeforeUpDown); }); } @@ -329,10 +375,7 @@ export default class Autosuggest extends Component { } willRenderSuggestions(props) { - const { suggestions, inputProps, shouldRenderSuggestions } = props; - const { value } = inputProps; - - return suggestions.length > 0 && shouldRenderSuggestions(value); + return willRenderSuggestionsAux(props); } storeAutowhateverRef = (autowhatever) => { @@ -343,16 +386,13 @@ export default class Autosuggest extends Component { onSuggestionMouseEnter = (event, { sectionIndex, itemIndex }) => { this.updateHighlightedSuggestion(sectionIndex, itemIndex); + let { justSelectedSuggestion } = this.state; if (event.target === this.pressedSuggestion) { - this.justSelectedSuggestion = true; + justSelectedSuggestion = true; } - this.justMouseEntered = true; - - setTimeout(() => { - this.justMouseEntered = false; - }); + this.setState({ justMouseEntered: true, justSelectedSuggestion }); }; highlightFirstSuggestion = () => { @@ -360,7 +400,7 @@ export default class Autosuggest extends Component { }; onDocumentMouseUp = () => { - if (this.pressedSuggestion && !this.justSelectedSuggestion) { + if (this.pressedSuggestion && !this.state.justSelectedSuggestion) { this.input.focus(); } this.pressedSuggestion = null; @@ -369,9 +409,9 @@ export default class Autosuggest extends Component { onSuggestionMouseDown = (event) => { // Checking if this.justSelectedSuggestion is already true to not duplicate touch events in chrome // See: https://github.com/facebook/react/issues/9809#issuecomment-413978405 - if (!this.justSelectedSuggestion) { - this.justSelectedSuggestion = true; + if (!this.state.justSelectedSuggestion) { this.pressedSuggestion = event.target; + this.setState({ justSelectedSuggestion: true }); } }; @@ -431,8 +471,8 @@ export default class Autosuggest extends Component { this.onBlur(); } - setTimeout(() => { - this.justSelectedSuggestion = false; + this.setState({ + justSelectedSuggestion: false, }); }; @@ -458,21 +498,21 @@ export default class Autosuggest extends Component { this.resetHighlightedSuggestion(false); // shouldResetValueBeforeUpDown if ( - this.justSelectedSuggestion && + this.state.justSelectedSuggestion && event.target === this.pressedSuggestion ) { - this.justSelectedSuggestion = false; + this.setState({ justSelectedSuggestion: false }); } }; onSuggestionTouchStart = () => { - this.justSelectedSuggestion = true; + this.setState({ justSelectedSuggestion: true }); // todo: event.preventDefault when https://github.com/facebook/react/issues/2043 // todo: gets released so onSuggestionMouseDown won't fire in chrome }; onSuggestionTouchMove = () => { - this.justSelectedSuggestion = false; + this.setState({ justSelectedSuggestion: false }); this.pressedSuggestion = null; this.input.focus(); }; @@ -544,7 +584,7 @@ export default class Autosuggest extends Component { ...inputProps, onFocus: (event) => { if ( - !this.justSelectedSuggestion && + !this.state.justSelectedSuggestion && !this.justClickedOnSuggestionsContainer ) { const shouldRender = shouldRenderSuggestions(value); @@ -569,7 +609,7 @@ export default class Autosuggest extends Component { this.blurEvent = event; - if (!this.justSelectedSuggestion) { + if (!this.state.justSelectedSuggestion) { this.onBlur(); this.onSuggestionsClearRequested(); } @@ -651,11 +691,7 @@ export default class Autosuggest extends Component { event.preventDefault(); // Prevents the cursor from moving - this.justPressedUpDown = true; - - setTimeout(() => { - this.justPressedUpDown = false; - }); + this.setState({ justPressedUpDown: true }); break; @@ -685,11 +721,7 @@ export default class Autosuggest extends Component { method: 'enter', }); - this.justSelectedSuggestion = true; - - setTimeout(() => { - this.justSelectedSuggestion = false; - }); + this.setState({ justSelectedSuggestion: true }); } break;