From a716b7266c2690523d2be66ea2ba174ae712f28a Mon Sep 17 00:00:00 2001 From: Ivar Nilsen Date: Fri, 9 Dec 2016 09:55:36 +0100 Subject: [PATCH] Wrap componentWillReceiveProps in setTimeout We're seeing an issue where the props that we pass also change when the value in the input-field does. This causes two problems 1) selectedItems gets overwritten again even though we've not changed the default selected account. This can be avoided by comparing the prop to see if it's actually changed. The react docs for componentWillReceiveProps also state that it may be called even if the props haven't changed and that checking for changes is a good idea in the first place. 2) Since the component is reading the inputValue from state, there's a chance it will get the previous value if setState was called from onInputChange in the same cycle of the event loop because setState is asynchronous. This is solved by wrapping the code in setTimeout, forcing it to run after we're sure the state has been settled. There might be a Better Way to do this but I'm not really up to the task right now. --- CHANGELOG.md | 3 +++ package.json | 3 ++- src/selectors/base-selector.js | 26 +++++++++++++++++++++----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8ad76a632..b4cbed6052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +# v3.0.4 +* Fix an esoteric bug that could cause the dropdown to display the wrong state if the value of the input was changed while the component was receiving a props-change + # v3.0.3 * Fix svg caret that was jumping up and down due to it's (slightly bigger) container being rotated instead of the actual icon itself diff --git a/package.json b/package.json index 7ebd871dca..0c04876e93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nfe-account-selector-react", - "version": "3.0.3", + "version": "3.0.4", "main": "lib/account-selector.js", "scripts": { "build": "babel -d lib/. --ignore=*.test.js src/.", @@ -75,6 +75,7 @@ "classnames": "^2.2.5", "ffe-checkbox-react": "^2.2.1", "ffe-icons-react": "0.4.2", + "lodash.isequal": "^4.4.0", "nfe-amount-formatter": "^2.0.1", "nfe-if": "^2.0.1", "react-scrollbar": "0.4.1" diff --git a/src/selectors/base-selector.js b/src/selectors/base-selector.js index dd2d015d8a..a8caa94732 100644 --- a/src/selectors/base-selector.js +++ b/src/selectors/base-selector.js @@ -1,5 +1,6 @@ import React, {PropTypes, Component} from 'react'; import classNames from 'classnames'; +import isEqual from 'lodash.isequal'; import ChevronIkon from 'ffe-icons-react/chevron-ikon'; import i18n from '../i18n/i18n'; import KryssIkon from 'ffe-icons-react/kryss-ikon'; @@ -53,11 +54,26 @@ class BaseSelector extends Component { } componentWillReceiveProps(props) { - const {inputValue} = this.state; - this.setState({ - filteredItems: this.filterItems(props.items, inputValue), - selectedItems: props.selectedItems - }); + // If we happen to change props as a result of the input-value change callback + // then this function will be called right after onInputChange but the inputValue + // in the state will not have been updated yet. Wrapping the code in setTimeout + // ensures that setState will have updated the state before we use it to filter + // the selection. + window.setTimeout(() => { + const {inputValue} = this.state; + + const nextState = { + filteredItems: this.filterItems(props.items, inputValue), + }; + + // Only update the selectedItems state if the props have actually changed + // to prevent accidentally reverting to the default selection. + if (!isEqual(props.selectedItems, this.props.selectedItems)) { + nextState.selectedItems = props.selectedItems; + } + + this.setState(nextState); + },0); } onReset(evt) {