I have the following issue with this codebase at https://github.com/codyc4321/dividends_ui on branch raise_term_state_to_main_component
. I am trying to raise the state of ‘term’ from just the search bar up to the main search page component on the homepage. I have the following code working on branch main
which is a functional component fully contained in SearchBar. On main
if the user is typing into the search it will not run a search until he has stopped typing for 2 seconds:
SearchPage.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';
const HOST = process.env.REACT_APP_HOSTNAME
const PROTOCOL = process.env.REACT_APP_PROTOCOL
const PORT = process.env.REACT_APP_PORT
const BASE_URL = PROTOCOL + '://' + HOST + ':' + PORT
class SearchPage extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
no_search_term: true,
recent_searches: ['wba'],
dividends_data: {
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: [],
name: '',
description: '',
}
}
}
updateStateData = (key, value) => {
const data = this.state.dividends_data;
data[key] = value;
this.setState({data});
}
addSearchTerm = (term) => {
const data = this.state.recent_searches;
if (!data.includes(term)) {
data.push(term)
this.setState({data});
}
}
addResponseKeys = (keys, response) => {
keys.map((key) => {
this.updateStateData(key, response.data[key]);
});
}
clearData = () => {
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: [],
});
}
runStockInfoSearch = async (term) => {
console.log("running search")
// clear old data
this.clearData();
if (term) {
this.setState({loading: true})
this.setState({no_search_term: false})
this.addSearchTerm(term);
const dividends_api_url = BASE_URL + '/dividends/' + term
console.log("hitting url to search- ", dividends_api_url)
axios.get(dividends_api_url, {})
.then(response => {
const RESPONSE_KEYS = [
'current_price',
'current_yield',
'recent_dividend_rate'
]
RESPONSE_KEYS.map((key) => {
this.updateStateData(key, response.data[key]);
});
this.updateStateData('all_dividends', response.data['all_dividends'].reverse());
const YEARS_CHANGE = [1, 3, 5, 10];
YEARS_CHANGE.map((year) => {
const key = 'dividend_change_' + year.toString() + '_year';
this.updateStateData(key, response.data[key]);
});
this.addResponseKeys(['name', 'summary', 'sector'], response);
this.setState({loading: false})
})
.catch(err => {
console.log(err);
})
} else {
this.setState({no_search_term: true})
}
}
renderMainContent() {
if (this.state.no_search_term === true) {
return (
<div className="ui active">
<div className="ui text">Search for info about a stock</div>
</div>
)
}
if (this.state.loading === true) {
return (
<div className="ui active dimmer">
<div className="ui text loader">Loading</div>
</div>
)
} else {
return (
<DividendResultsDisplay data={this.state.dividends_data}/>
)
}
}
renderRecentSearches() {
return this.state.recent_searches.map((term) => {
return (
<button style={{marginRight: '10px'}}>{term}</button>
)
})
}
render() {
return (
<div className="ui container" style={{marginTop: '10px'}}>
<SearchBar runSearch={this.runStockInfoSearch} />
{this.renderRecentSearches()}
<div className="ui segment">
{this.renderMainContent()}
</div>
</div>
)
if (this.state.loading === true) {
return (
<div className="ui container" style={{marginTop: '10px'}}>
<SearchBar runSearch={this.runStockInfoSearch} />
<div className="ui segment">
<div className="ui active dimmer">
<div className="ui text loader">Loading</div>
</div>
</div>
</div>
)
} else {
return (
<div className="ui container" style={{marginTop: '10px'}}>
<SearchBar runSearch={this.runStockInfoSearch} />
<DividendResultsDisplay
data={this.state.dividends_data}
/>
</div>
)
}
}
}
export default SearchPage;
SearchBar.js:
import React, {useState, useEffect} from 'react';
const SearchBar = ({runSearch}) => {
const defaultStock = 'wba';
const [term, setTerm] = useState(defaultStock);
const [debouncedTerm, setDebouncedTerm] = useState(defaultStock)
const onFormSubmit = (event) => {
event.preventDefault();
}
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 800);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {runSearch(term)}, [debouncedTerm]);
return (
<div className="ui segment">
<form onSubmit={onFormSubmit} className="ui form">
<div className="field">
<label>Stock search</label>
<input
type="text"
value={term}
onChange={(e) => setTerm(e.target.value)}
/>
</div>
</form>
</div>
);
}
export default SearchBar;
.env:
REACT_APP_HOSTNAME=localhost
REACT_APP_PROTOCOL=http
REACT_APP_PORT=8000
My attempt at moving the state up to the main component is not working. I didn’t want search bar to actually run the search because I now needed buttons to be able to update the term and run a new search:
SearchPage.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';
const HOST = process.env.REACT_APP_HOSTNAME
const PROTOCOL = process.env.REACT_APP_PROTOCOL
const PORT = process.env.REACT_APP_PORT
const BASE_URL = PROTOCOL + '://' + HOST + ':' + PORT
class SearchPage extends React.Component {
debounceTimerId = undefined;
constructor(props) {
super(props);
this.state = {
term: 'wba',
debouncedTerm: 'wba',
timerId: undefined,
loading: false,
no_search_term: true,
recent_searches: ['wba'],
dividends_data: {
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: [],
name: '',
description: '',
}
}
}
// clearDebounceTimer() {
// if (this.debounceTimerId) new Promise(function(resolve, reject) {
// clearTimeout(this.debounceTimerId);
// this.debounceTimerId = undefined;
// });
// }
clearDebounceTimer() {
if (this.state.timerId) new Promise(function(resolve, reject) {
clearTimeout(this.state.timerId);
this.state.timerId = undefined;
});
}
componentDidUpdate(previousProps, previousState) {
if (this.state.term !== previousState.term) {
// if (this.state.debouncedTerm !== previousState.debouncedTerm) {
this.clearDebounceTimer();
this.state.timerId = setTimeout(() => {
// this.debounceTimerId = setTimeout(() => {
this.setState({debouncedTerm: this.state.term})
}, 2500)
}
if (this.state.debouncedTerm !== previousState.debouncedTerm) {
this.runStockInfoSearch(this.state.term);
}
}
componentWillUnmount() {
// clearTimeout(this.state.timerId);
clearTimeout(this.debounceTimerId);
}
onTermUpdate = (term) => {
this.setState({term: term})
}
updateStateData = (key, value) => {
const data = this.state.dividends_data;
data[key] = value;
this.setState({data});
}
addSearchTerm = (term) => {
const data = this.state.recent_searches;
if (!data.includes(term)) {
data.push(term)
this.setState({data});
}
}
addResponseKeys = (keys, response) => {
keys.map((key) => {
this.updateStateData(key, response.data[key]);
});
}
clearData = () => {
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: [],
});
}
runStockInfoSearch = async (term) => {
console.log("running search")
// clear old data
this.clearData();
if (term) {
this.setState({loading: true})
this.setState({no_search_term: false})
this.addSearchTerm(term);
const dividends_api_url = BASE_URL + '/dividends/' + term
console.log("hitting url to search- ", dividends_api_url)
axios.get(dividends_api_url, {})
.then(response => {
const RESPONSE_KEYS = [
'current_price',
'current_yield',
'recent_dividend_rate'
]
RESPONSE_KEYS.map((key) => {
this.updateStateData(key, response.data[key]);
});
this.updateStateData('all_dividends', response.data['all_dividends'].reverse());
const YEARS_CHANGE = [1, 3, 5, 10];
YEARS_CHANGE.map((year) => {
const key = 'dividend_change_' + year.toString() + '_year';
this.updateStateData(key, response.data[key]);
});
this.addResponseKeys(['name', 'summary', 'sector'], response);
this.setState({loading: false})
})
.catch(err => {
console.log(err);
})
} else {
this.setState({no_search_term: true})
}
}
renderMainContent() {
if (this.state.no_search_term === true) {
return (
<div className="ui active">
<div className="ui text">Search for info about a stock</div>
</div>
)
}
if (this.state.loading === true) {
return (
<div className="ui active dimmer">
<div className="ui text loader">Loading</div>
</div>
)
} else {
return (
<DividendResultsDisplay data={this.state.dividends_data}/>
)
}
}
renderRecentSearches() {
return this.state.recent_searches.map((term) => {
return (
<button onClick={() => this.onTermUpdate(term)} style={{marginRight: '10px'}}>{term}</button>
)
})
}
render() {
console.log("term in higher state: ", this.state.term);
return (
<div className="ui container" style={{marginTop: '10px'}}>
<SearchBar
runSearch={this.runStockInfoSearch}
term={this.state.term}
onTermUpdate={this.onTermUpdate}/>
{this.renderRecentSearches()}
<div className="ui segment">
{this.renderMainContent()}
</div>
</div>
)
}
}
export default SearchPage;
SearchBar.js:
import React, {useState, useEffect} from 'react';
const SearchBar = ({runSearch, onTermUpdate, term}) => {
const onFormSubmit = (event) => {
event.preventDefault();
}
return (
<div className="ui segment">
<form onSubmit={onFormSubmit} className="ui form">
<div className="field">
<label>Stock search</label>
<input
type="text"
value={term}
onChange={(e) => onTermUpdate(e.target.value)}
/>
</div>
</form>
</div>
);
}
export default SearchBar;
In this attempt in a class component after 2 seconds of typing searches will just continuously run after each key pressed, instead of waiting 2 seconds after typing has stopped. I can probably rewrite it as a function but I would prefer to keep the higher component a class for cleanness and ease of reading the code. Thank you for any help