Performance improvements for large lists of recipient tokens in composer

This commit is contained in:
Ben Gotow 2019-10-26 13:09:35 -05:00
parent 5710743916
commit 627ca8fadc

View file

@ -16,10 +16,7 @@ type SizeToFitInputProps = {
class SizeToFitInput extends React.Component< class SizeToFitInput extends React.Component<
SizeToFitInputProps & React.HTMLProps<HTMLInputElement> SizeToFitInputProps & React.HTMLProps<HTMLInputElement>
> { > {
constructor(props) { state = {};
super(props);
this.state = {};
}
componentDidMount() { componentDidMount() {
this._sizeToFit(); this._sizeToFit();
@ -59,8 +56,8 @@ class SizeToFitInput extends React.Component<
}; };
} }
focus() { focus(args?: FocusOptions) {
this.inputEl.focus(); this.inputEl.focus(args);
} }
render() { render() {
@ -74,8 +71,7 @@ class SizeToFitInput extends React.Component<
} }
interface TokenState { interface TokenState {
editing: boolean; editing: string | null;
editingValue: string;
dragging: boolean; dragging: boolean;
} }
@ -112,25 +108,24 @@ class Token extends React.Component<TokenProps, TokenState> {
className: '', className: '',
}; };
constructor(props) { state = {
super(props); editing: null,
this.state = { dragging: false,
editing: false, };
dragging: false,
editingValue: this.props.item.toString(), shouldComponentUpdate(nextProps: TokenProps, nextState: TokenState) {
}; return (
nextProps.selected !== this.props.selected ||
nextProps.valid !== this.props.valid ||
nextProps.item !== this.props.item ||
nextProps.disabled !== this.props.disabled ||
nextState.editing !== this.state.editing ||
nextState.dragging !== this.state.dragging
);
} }
componentWillReceiveProps(props) { componentDidUpdate(prevProps: TokenProps, prevState: TokenState) {
// never override the text the user is editing if they're looking at it if (this.state.editing !== null && prevState.editing === null) {
if (this.state.editing) {
return;
}
this.setState({ editingValue: props.item.toString() });
}
componentDidUpdate(prevProps, prevState) {
if (this.state.editing && !prevState.editing) {
(this.refs.input as SizeToFitInput).select(); (this.refs.input as SizeToFitInput).select();
} }
} }
@ -141,10 +136,10 @@ class Token extends React.Component<TokenProps, TokenState> {
ref="input" ref="input"
className="token-editing-input" className="token-editing-input"
spellCheck={false} spellCheck={false}
value={this.state.editingValue} value={this.state.editing !== null ? this.state.editing : this.props.item.toString()}
onKeyDown={this._onEditKeydown} onKeyDown={this._onEditKeydown}
onBlur={this._onEditFinished} onBlur={this._onEditFinished}
onChange={event => this.setState({ editingValue: event.currentTarget.value })} onChange={event => this.setState({ editing: event.currentTarget.value })}
/> />
); );
} }
@ -204,7 +199,7 @@ class Token extends React.Component<TokenProps, TokenState> {
this.props.onEditMotion(this.props.item); this.props.onEditMotion(this.props.item);
} }
if (this.props.onEdited) { if (this.props.onEdited) {
this.setState({ editing: true }); this.setState({ editing: this.props.item.toString() });
} }
}; };
@ -221,9 +216,9 @@ class Token extends React.Component<TokenProps, TokenState> {
_onEditFinished = () => { _onEditFinished = () => {
if (this.props.disabled) return; if (this.props.disabled) return;
if (this.props.onEdited) { if (this.props.onEdited) {
this.props.onEdited(this.props.item, this.state.editingValue); this.props.onEdited(this.props.item, this.state.editing);
} }
this.setState({ editing: false }); this.setState({ editing: null });
}; };
_onAction = event => { _onAction = event => {
@ -233,7 +228,7 @@ class Token extends React.Component<TokenProps, TokenState> {
}; };
render() { render() {
return this.state.editing ? this._renderEditing() : this._renderViewing(); return this.state.editing !== null ? this._renderEditing() : this._renderViewing();
} }
} }
@ -470,7 +465,11 @@ export class TokenizingTextField extends React.Component<
if (event.target.tagName === 'INPUT' && ReactDOM.findDOMNode(this).contains(event.target)) { if (event.target.tagName === 'INPUT' && ReactDOM.findDOMNode(this).contains(event.target)) {
return; return;
} }
this.focus();
// We will focus on the field when they type the first character,
// but the input may contain a ton of items and interacting with the field
// shouldn't scroll to the bottom of it.
this.focus({ preventScroll: true });
}; };
_onDrop = event => { _onDrop = event => {
@ -665,8 +664,8 @@ export class TokenizingTextField extends React.Component<
this._refreshCompletions('', { clear: true }); this._refreshCompletions('', { clear: true });
} }
focus() { focus(args?: FocusOptions) {
(this.refs.input as HTMLElement).focus(); (this.refs.input as SizeToFitInput).focus(args);
} }
// Managing Tokens // Managing Tokens