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>
)}
/>