I have the following app at https://github.com/codyc4321/dividends_ui on branch debounce_search_term. The server can be downloaded at https://github.com/codyc4321/stocks_backend. I have the following app which handle higher level information, the execution of the search, and passing data to dumb displays:
App.js
import React from 'react';
import SearchBar from './SearchBar';
import AllDividendsDisplay from './dividend_results_display/AllDividendsDisplay';
import DividendResultsDisplay from './dividend_results_display/DividendResultsDisplay';
import axios from 'axios';
class App extends React.Component {
state = {
current_price: '',
recent_dividend_rate: '',
current_yield: '',
dividend_change_1_year: '',
dividend_change_3_year: '',
dividend_change_5_year: '',
dividend_change_10_year: '',
all_dividends: [],
}
onSearchSubmit = async (term) => {
// clear old data
// this.setState({
// current_price: '',
// recent_dividend_rate: '',
// current_yield: '',
// dividend_change_1_year: '',
// dividend_change_3_year: '',
// dividend_change_5_year: '',
// dividend_change_10_year: '',
// all_dividends: [],
// });
const host = 'localhost';
const base_url = 'http://' + host + ':8000'
console.log(base_url)
const price_url = base_url + '/dividends/current_price/' + term
const recent_rate_url = base_url + '/dividends/recent_dividend_rate/' + term
const yield_url = base_url + '/dividends/current_yield/' + term
const REQUEST_MAPPER = [
{
url: price_url,
state_key: 'current_price',
response_key: 'current_price'
},
{
url: recent_rate_url,
state_key: 'recent_dividend_rate',
response_key: 'year_dividend_rate'
},
{
url: yield_url,
state_key: 'current_yield',
response_key: 'current_yield'
},
];
REQUEST_MAPPER.map(request_data => {
axios.get(request_data.url, {})
.then(response => {
this.setState({[request_data.state_key]: response.data[request_data.response_key]});
})
.catch(err => {
console.log(err);
})
});
const YEARS = [1, 3, 5, 10];
YEARS.map(year => {
const URL = base_url + '/dividends/dividend_yield_change/' + term + '/' + year.toString();
const OBJECT_KEY = 'dividend_change_' + year.toString() + '_year';
axios.get(URL, {})
.then(response => {
this.setState({[OBJECT_KEY]: response.data['change']});
})
.catch(err => {
console.log(err);
});
});
const all_dividends_url = base_url + '/dividends/all_dividends/' + term + '/3';
axios.get(all_dividends_url, {})
.then(response => {
this.setState({all_dividends: response.data.reverse()});
})
.catch(err => {
console.log(err);
});
}
render() {
return (
<div className="ui container" style={{marginTop: '10px'}}>
<SearchBar onSubmit={this.onSearchSubmit} />
<DividendResultsDisplay
current_price={this.state.current_price}
recent_dividend_rate={this.state.recent_dividend_rate}
current_yield={this.state.current_yield}
dividend_change_1_year={this.state.dividend_change_1_year}
dividend_change_3_year={this.state.dividend_change_3_year}
dividend_change_5_year={this.state.dividend_change_5_year}
dividend_change_10_year={this.state.dividend_change_10_year}
all_dividends={this.state.all_dividends}
/>
</div>
)
}
}
export default App;
I am trying to implement a search that runs after 2 seconds of the user not typing, each time the search term changes (with a 2.5 sec delay of not typing):
SearchBar.js:
import React from 'react';
class SearchBar extends React.Component {
state = {
term: 'psec',
debouncedTerm: 'psec',
debounceTimerId: 0
}
componentDidUpdate(previousProps, previousState) {
// this.props.onSubmit(this.state.term);
console.log("new state:");
console.log(this.state);
console.log("previous state:");
console.log(previousState);
console.log("props:");
console.log(this.props);
console.log("previous props");
console.log(previousProps);
// this.props.onSubmit(this.state.term);
const timerId = setTimeout(() => {
this.setState({debouncedTerm: this.state.term})
}, 2500);
this.setState({debounceTimerId: timerId})
if (this.state.debouncedTerm !== previousState.debouncedTerm) {
this.props.onSubmit(this.state.term);
}
}
componentWillUnmount() {
clearTimeout(this.state.timerId);
}
onFormSubmit = (event) => {
event.preventDefault();
this.props.onSubmit(this.state.term)
}
render() {
return (
<div className="ui segment">
<form onSubmit={this.onFormSubmit} className="ui form">
<div className="field">
<label>Stock search</label>
<input
type="text"
value={this.state.term}
onChange={(e) => this.setState({term: e.target.value})}
ref={ref => ref && ref.focus()}
onfocus={(e)=>e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length)}
/>
</div>
</form>
</div>
);
};
};
export default SearchBar;
I am getting the following infinite loop error:
react-dom.development.js:27292 Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I am trying to implement this feature which the course implemented in functional components, not classes. I would like to search when the user changes their search term, after two second delay.
Konrad's comment is bang-on - calling setState
in your componentDidUpdate
callback is causing the infinite loop.
You need to remove timerId
from your state and make it a class variable instead; something like this:
class SearchBar extends React.Component {
debounceTimerId = undefined;
state = {
term: 'psec',
debouncedTerm: 'psec',
}
clearDebounceTimer() {
if (this.debounceTimerId) {
clearTimeout(this.debounceTimerId);
this.debounceTimerId = undefined;
}
}
componentDidUpdate(previousProps, previousState) {
// Don't update the timer unless the term has changed
if (this.state.term !== previousState.term) {
// Make sure you clear the existing timer first!
this.clearDebounceTimer();
// Updating a class variable won't force a re-render
this.debounceTimerId = setTimeout(() => {
this.setState({ debouncedTerm: this.state.term })
}, 2500);
};
}
}