From 6dfb39e888e2ce0de6b24708b369502e3c30562e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Insua=20Ya=C3=B1ez?= Date: Mon, 20 Apr 2020 02:46:12 +0200 Subject: [PATCH] fix: refactor code to use getDerivedStateFromProps --- src/Autosuggest.js | 276 +++++++++++++++++++++++++-------------------- 1 file changed, 154 insertions(+), 122 deletions(-) diff --git a/src/Autosuggest.js b/src/Autosuggest.js index 0d6a1522..11eec17b 100644 --- a/src/Autosuggest.js +++ b/src/Autosuggest.js @@ -5,10 +5,55 @@ import Autowhatever from 'react-autowhatever'; import { defaultTheme, mapToAutowhateverTheme } from './theme'; const alwaysTrue = () => true; -const defaultShouldRenderSuggestions = value => value.trim().length > 0; +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 = { @@ -81,7 +126,7 @@ export default class Autosuggest extends Component { focusInputOnSuggestionClick: PropTypes.bool, highlightFirstSuggestion: PropTypes.bool, theme: PropTypes.object, - id: PropTypes.string + id: PropTypes.string, }; static defaultProps = { @@ -92,62 +137,85 @@ export default class Autosuggest extends Component { focusInputOnSuggestionClick: true, highlightFirstSuggestion: false, theme: defaultTheme, - id: '1' + id: '1', }; - constructor({ alwaysRenderSuggestions }) { + constructor(props) { super(); this.state = { isFocused: false, - isCollapsed: !alwaysRenderSuggestions, + isCollapsed: !props.alwaysRenderSuggestions, highlightedSectionIndex: null, highlightedSuggestionIndex: null, highlightedSuggestion: null, - valueBeforeUpDown: 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) { const { suggestions, onSuggestionHighlighted, - highlightFirstSuggestion + highlightFirstSuggestion, } = this.props; if ( @@ -165,7 +233,7 @@ export default class Autosuggest extends Component { if (highlightedSuggestion != prevHighlightedSuggestion) { onSuggestionHighlighted({ - suggestion: highlightedSuggestion + suggestion: highlightedSuggestion, }); } } @@ -177,48 +245,26 @@ 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 - }; + this.setState((state) => { + 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 - }; + this.setState((state) => { + return resetHighlightedSuggestionAux(state, shouldResetValueBeforeUpDown); }); } revealSuggestions() { this.setState({ - isCollapsed: false + isCollapsed: false, }); } @@ -228,7 +274,7 @@ export default class Autosuggest extends Component { highlightedSuggestionIndex: null, highlightedSuggestion: null, valueBeforeUpDown: null, - isCollapsed: true + isCollapsed: true, }); } @@ -272,11 +318,11 @@ export default class Autosuggest extends Component { return { sectionIndex: typeof sectionIndex === 'string' ? parseInt(sectionIndex, 10) : null, - suggestionIndex: parseInt(suggestionIndex, 10) + suggestionIndex: parseInt(suggestionIndex, 10), }; } - onDocumentMouseDown = event => { + onDocumentMouseDown = (event) => { this.justClickedOnSuggestionsContainer = false; let node = @@ -329,13 +375,10 @@ 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 => { + storeAutowhateverRef = (autowhatever) => { if (autowhatever !== null) { this.autowhatever = 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,18 +400,18 @@ export default class Autosuggest extends Component { }; onDocumentMouseUp = () => { - if (this.pressedSuggestion && !this.justSelectedSuggestion) { + if (this.pressedSuggestion && !this.state.justSelectedSuggestion) { this.input.focus(); } this.pressedSuggestion = null; }; - onSuggestionMouseDown = event => { + 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 }); } }; @@ -385,7 +425,7 @@ export default class Autosuggest extends Component { const { alwaysRenderSuggestions, onSuggestionSelected, - onSuggestionsFetchRequested + onSuggestionsFetchRequested, } = this.props; onSuggestionSelected && onSuggestionSelected(event, data); @@ -393,7 +433,7 @@ export default class Autosuggest extends Component { if (alwaysRenderSuggestions) { onSuggestionsFetchRequested({ value: data.suggestionValue, - reason: 'suggestion-selected' + reason: 'suggestion-selected', }); } else { this.onSuggestionsClearRequested(); @@ -402,7 +442,7 @@ export default class Autosuggest extends Component { this.resetHighlightedSuggestion(); }; - onSuggestionClick = event => { + onSuggestionClick = (event) => { const { alwaysRenderSuggestions, focusInputOnSuggestionClick } = this.props; const { sectionIndex, suggestionIndex } = this.getSuggestionIndices( this.findSuggestionElement(event.target) @@ -418,7 +458,7 @@ export default class Autosuggest extends Component { suggestionValue: clickedSuggestionValue, suggestionIndex: suggestionIndex, sectionIndex, - method: 'click' + method: 'click', }); if (!alwaysRenderSuggestions) { @@ -431,8 +471,8 @@ export default class Autosuggest extends Component { this.onBlur(); } - setTimeout(() => { - this.justSelectedSuggestion = false; + this.setState({ + justSelectedSuggestion: false, }); }; @@ -448,31 +488,31 @@ export default class Autosuggest extends Component { highlightedSuggestionIndex: null, highlightedSuggestion: null, valueBeforeUpDown: null, - isCollapsed: !shouldRender + isCollapsed: !shouldRender, }); onBlur && onBlur(this.blurEvent, { highlightedSuggestion }); }; - onSuggestionMouseLeave = event => { + onSuggestionMouseLeave = (event) => { 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(); }; @@ -486,7 +526,7 @@ export default class Autosuggest extends Component { onMouseDown: this.onSuggestionMouseDown, onTouchStart: this.onSuggestionTouchStart, onTouchMove: this.onSuggestionTouchMove, - onClick: this.onSuggestionClick + onClick: this.onSuggestionClick, }; }; @@ -504,7 +544,7 @@ export default class Autosuggest extends Component { return renderSuggestionsContainer({ containerProps, children, - query: this.getQuery() + query: this.getQuery(), }); }; @@ -522,14 +562,14 @@ export default class Autosuggest extends Component { theme, getSuggestionValue, alwaysRenderSuggestions, - highlightFirstSuggestion + highlightFirstSuggestion, } = this.props; const { isFocused, isCollapsed, highlightedSectionIndex, highlightedSuggestionIndex, - valueBeforeUpDown + valueBeforeUpDown, } = this.state; const shouldRenderSuggestions = alwaysRenderSuggestions ? alwaysTrue @@ -542,16 +582,16 @@ export default class Autosuggest extends Component { const items = isOpen ? suggestions : []; const autowhateverInputProps = { ...inputProps, - onFocus: event => { + onFocus: (event) => { if ( - !this.justSelectedSuggestion && + !this.state.justSelectedSuggestion && !this.justClickedOnSuggestionsContainer ) { const shouldRender = shouldRenderSuggestions(value); this.setState({ isFocused: true, - isCollapsed: !shouldRender + isCollapsed: !shouldRender, }); onFocus && onFocus(event); @@ -561,7 +601,7 @@ export default class Autosuggest extends Component { } } }, - onBlur: event => { + onBlur: (event) => { if (this.justClickedOnSuggestionsContainer) { this.input.focus(); return; @@ -569,12 +609,12 @@ export default class Autosuggest extends Component { this.blurEvent = event; - if (!this.justSelectedSuggestion) { + if (!this.state.justSelectedSuggestion) { this.onBlur(); this.onSuggestionsClearRequested(); } }, - onChange: event => { + onChange: (event) => { const { value } = event.target; const shouldRender = shouldRenderSuggestions(value); @@ -590,10 +630,10 @@ export default class Autosuggest extends Component { : { highlightedSectionIndex: null, highlightedSuggestionIndex: null, - highlightedSuggestion: null + highlightedSuggestion: null, }), valueBeforeUpDown: null, - isCollapsed: !shouldRender + isCollapsed: !shouldRender, }); if (shouldRender) { @@ -612,14 +652,14 @@ export default class Autosuggest extends Component { if (shouldRenderSuggestions(value)) { onSuggestionsFetchRequested({ value, - reason: 'suggestions-revealed' + reason: 'suggestions-revealed', }); this.revealSuggestions(); } } else if (suggestions.length > 0) { const { newHighlightedSectionIndex, - newHighlightedItemIndex + newHighlightedItemIndex, } = data; let newValue; @@ -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; @@ -682,14 +718,10 @@ export default class Autosuggest extends Component { suggestionValue: newValue, suggestionIndex: highlightedSuggestionIndex, sectionIndex: highlightedSectionIndex, - method: 'enter' + method: 'enter', }); - this.justSelectedSuggestion = true; - - setTimeout(() => { - this.justSelectedSuggestion = false; - }); + this.setState({ justSelectedSuggestion: true }); } break; @@ -717,7 +749,7 @@ export default class Autosuggest extends Component { if (shouldRenderSuggestions(newValue)) { onSuggestionsFetchRequested({ value: newValue, - reason: 'escape-pressed' + reason: 'escape-pressed', }); } else { this.onSuggestionsClearRequested(); @@ -740,10 +772,10 @@ export default class Autosuggest extends Component { } onKeyDown && onKeyDown(event); - } + }, }; const renderSuggestionData = { - query: this.getQuery() + query: this.getQuery(), }; return (