debounced term design not working once I moved to a class component

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

enter image description here