I’m encountering a problem with a React component where using setState seems to cause unexpected behavior. Specifically, when I update the state with setState, the dropdown in my Select component (from BaseUI) closes and the input value is cleared, even though I’m only intending to update the state with a new value from the input field.
Expected behavior:
I expect the dropdown to stay open and the input value to remain as I type into the Select component’s input field. I’m trying to make a spinner in the dropdown menu and also limit the number of options in the dropdown menu.
Actual behavior:
Every time I type in the Select component’s input field, the dropdown closes and the input value clears. This happens immediately after setState is called in handleInputChange.
Additional context:
I’m using Redux form to manage form state.
The Select component is from BaseUI.
Question:
What could be causing setState to behave this way, and how can I ensure that updating any states doesn’t interfere with the Select component’s dropdown and input field?
// @noflow
import React, { Component } from 'react';
import { reduxForm, Field } from 'redux-form';
import { compose, bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import get from 'lodash/get';
import validate from '../pipelineValidation';
import { Button, Linkify, Spinner } from '@uber/phase-ui';
import {
validateKafkaSource,
validateKafkaVolume,
} from '../../../reducers/pipeline';
import { Select } from 'baseui/select';
import { StyledLink } from 'baseui/link';
import { Label2 } from 'baseui/typography';
class KafkaPipelineSource extends Component {
constructor(props) {
super(props);
this.state = {
kafkaTopicLists: [],
loading: false, // Track loading state for spinner
};
}
handleSelectChange = ({ value }) => {
const selectedLabel = value.length > 0 ? value[0].label : '';
const { change } = this.props;
change('kafkaTopic', selectedLabel); // Update form value with selected label
};
handleInputChange = event => {
const inputValue = event.target.value.toLowerCase(); // Get input value and convert to lowercase
// Update state only if input value changes to prevent unnecessary re-renders
this.setState({ loading: true }, () => {
// Simulate asynchronous filtering (replace with actual logic)
setTimeout(() => {
const filteredOptions = this.props.kafkaTopicList
.filter(option => option.toLowerCase().includes(inputValue))
.slice(0, 100)
.map(value => ({
id: value,
label: value,
}));
this.setState({
kafkaTopicLists: filteredOptions,
loading: false, // Set loading state to false after filtering
});
}, 300); // Simulating a delay for filtering
});
};
validateKafkaTopic = () => {
const { kafkaTopic, validateKafkaSource } = this.props;
validateKafkaSource({
datasetName: kafkaTopic,
});
};
validateKafkaVolume = () => {
const { kafkaTopic, validateKafkaVolume } = this.props;
validateKafkaVolume({
datasetName: kafkaTopic,
});
};
render() {
const {
kafkaTopicList = [],
previousPage,
handleSubmit,
kafkaTopic,
kafkaSource,
sinkType,
kafkaVolume,
} = this.props;
const { kafkaTopicLists, loading } = this.state;
console.log('kafkaTopic:', kafkaTopic);
return (
<form className="mm-top" onSubmit={handleSubmit}>
<Label2>Heatpipe Kafka Topic</Label2>
<div>
Choose heatpipe avro or protobuf topic to ingest. If you do not have
one yet, create one.
</div>
<Field
name="kafkaTopic"
component={({ meta: { touched, error, warning } }) => (
<div>
<Select
ariaLabel="kafkaTopic"
options={kafkaTopicLists}
value={kafkaTopicLists.find(o => o.id === kafkaTopic)}
placeholder="Select..."
labelKey="id"
valueKey="label"
onChange={this.handleSelectChange}
onInputChange={this.handleInputChange}
searchable
overrides={{
Dropdown: {
style: {
maxHeight: '300px',
},
},
}}
/>
{loading && <Spinner size={24} />}
{touched &&
((error && <span>{error}</span>) ||
(warning && <span>{warning}</span>))}
</div>
)}
/>
2
Answers
That behaviour that you are getting—the closing of the dropdown and clearing of the input value when updating the state—is because of the way that React works with state and component re-renders.
On calling setState, a re-render of the component is triggered, where this dropdown could now close and the input reset. To avoid closing the dropdown and resetting the input using setState, debounce the
handleInputChange
method. This reduces re-renders and maintains the input field’s value.Here’s a quick implementation:
Install the lodash.debounce package:
npm install lodash.debounce
Import and use debounce in your
handleInputChange
method:I don’t have any experience with Base UI, but the input probably resets because of what you set as
value
property of theSelect
component:According to the docs, the
value
should always be an array. If I were to guess, there is probably a warning in your Console about this.Unfortunately, I don’t know why the dropdown would close.