0

I have introduced some serious breaking bugs into my react app somehow, and I do not understand how to fix it. Yesterday the data from runSearch was showing up to the main component SearchPage, but today the search will not even run at all. The SearchPage defaults the data to empty strings, meaning at worst the page would display tables without any data, but for some reason the data passed to the children becomes undefined, not empty strings. As an error pops up due to undefined values, the search for info does not even run at all

SearchPage.js

import React, {useState, useEffect} from 'react';
import { connect } from 'react-redux';

import axios from 'axios';

import SearchBar from './SearchBar';
// import AllDividendsDisplay from './dividend_results_display/AllDividendsDisplay';
import DividendResultsDisplay from './dividend_results_display/DividendResultsDisplay';
import AllEarningsDisplay from './earnings_results_display/AllEarningsDisplay';
import SettingsView from './settings/SettingsView';
import debounceTerm from '../hooks';


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

const SearchPage = ({userId}) => {

  const DEFAULT_STOCK = 'ibm';
  const [term, setTerm] = useState();
  const [debouncedTerm, setDebouncedTerm] = useState();
  const [loading, setLoading] = useState(false);
  const [recentSearches, setRecentSearches] = useState([DEFAULT_STOCK]);
  const [dividendsYearsBack, setDividendsYearsBack] = useState('3');
  const [debouncedDividendYearsBack, setDebouncedDividendYearsBack] = useState('3');
  const [errorMessage, setErrorMessage] = useState('');
  const [dividendsData, setDividendsData] = useState(
    {
      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: '',
    }
  )
  const [settingsViewVisible, setSettingsViewVisible] = useState(false);

  // const [showYieldChange, setShowYieldChange] = useState(true);
  // const [showAllDividends, setShowAllDividends] = useState(true);
  const [displaySettings, setDisplaySettings] = useState([
      {setting_name: 'showYieldChange', visible: true},
      {setting_name: 'showAllDividends', visible: true},
  ])

  const onTermUpdate = (term) => {
    const trimmed = term.trim()
    setTerm(trimmed);
  }

  debounceTerm(setDebouncedTerm, term, 1500);
  debounceTerm(setDebouncedDividendYearsBack, dividendsYearsBack, 1500);

  useEffect(() => {runSearch()}, [debouncedTerm]);

  useEffect(() => {
    // alert(dividendsYearsBack)
    if (dividendsYearsBack !== '') {
      runSearch();
    }
  }, [debouncedDividendYearsBack])

  useEffect(() => {
    console.log("user id changed")
    if (userId) {
      const user_profile_api_url = BASE_URL + '/users/' + userId
      axios.get(user_profile_api_url, {})
        .then(response => {
          console.log(response)

          const recent_searches_response = response.data.searches;
          const new_recent_searches = [];
          recent_searches_response.map(dict => {
            new_recent_searches.push(dict.search_term)
          })
          setRecentSearches(new_recent_searches);

          setDisplaySettings(response.data.display_settings);
        })
        .catch((error) => {
          console.log("error in getting user profile: ", error.message)
        })
    }
  }, [userId])

  useEffect(() => {
    const user_profile_api_url = BASE_URL + '/users/' + userId
    const request_data = {
      searches: recentSearches,
      display_settings: displaySettings
    }

    axios.post(user_profile_api_url, request_data)
      // .then(response => {
      //   console.log(response)
      // })

  }, [recentSearches, displaySettings])

  const makeSearchApiRequest = () => {
    let dividends_api_url = BASE_URL + '/dividends/' + term + '/' + dividendsYearsBack
    console.log(dividends_api_url);

    if (!recentSearches.includes(term)) {
      setRecentSearches([...recentSearches, term])
    }

    axios.get(dividends_api_url, {})
      .then(response => {
        // console.log(response.data)
        setLoading(false);
        setDividendsData(response.data);
      })
      .catch((error) => {
        console.log(error.message);
        setLoading(false);
        setErrorMessage(error.message);
      })
  }

  const runSearch = () => {
    console.log("running search: ", term);
    setErrorMessage('');

    if (term) {

      setLoading(true);

      if (!dividendsYearsBack) {
        setDividendsYearsBack('3', () => {
          makeSearchApiRequest()
        });
      } else {
        makeSearchApiRequest()
      }
    }
  }

  const recentSearchOnClick = (term) => {
    setTerm(term);
    setDebouncedTerm(term);
  }

  const removeRecentSearchOnClick = (term) => {
    const searchesWithoutThisOne = recentSearches.filter(search => search !== term)
    setRecentSearches(searchesWithoutThisOne);
  }

  const dividendsYearsBackOnChange = (text) => {
    const trimmed = text.trim()
    setDividendsYearsBack(trimmed);
  }

  const generateShowToggler = (setter, state) => {
    return function() {
      setter(!state);
    }
  }

  const generateToggleDisplaySetting = (setting_name) => {
    return function() {
      const otherSettings = displaySettings.filter((dict) => dict.setting_name !== setting_name);
      const specifiedSetting = displaySettings.find((dict) => dict.setting_name == setting_name);
      specifiedSetting.visible = !specifiedSetting.visible
      const newDisplaySettings = [...otherSettings, specifiedSetting]
      setDisplaySettings(newDisplaySettings)
    }
  }

  const renderMainContent = () => {
    if (!debouncedTerm) {
      return (
        <div className="ui active">
          <div className="ui text">Search for info about a stock</div>
        </div>
      )
    }
    if (loading === true) {
      return (
        <div className="ui active dimmer">
          <div className="ui big text loader">Loading</div>
        </div>
      )
    }
    if (errorMessage) {
      return (
        <div className="ui active">
          <div className="ui text">{errorMessage}</div>
        </div>
      )
    } else {
      return (
        <div>
          <DividendResultsDisplay
            dividendsData={dividendsData}
            dividends_years_back={dividendsYearsBack}
            dividendsYearsBackOnChange={dividendsYearsBackOnChange}
            displaySettings={displaySettings}
            toggleYieldChange={generateToggleDisplaySetting('showYieldChange')}
            toggleAllDividends={generateToggleDisplaySetting('showAllDividends')}/>
          <br/>
          <AllEarningsDisplay earnings={dividendsData.earnings} />
        </div>
      )
    }
  }

  // https://stackoverflow.com/questions/38619981/how-can-i-prevent-event-bubbling-in-nested-react-components-on-click
  const renderRecentSearches = () => {
    return recentSearches.map((term) => {
      return (
        <div key={term}>
          <button
            onClick={() => recentSearchOnClick(term)}
            style={{marginRight: '10px'}}
            >
              <div>{term} </div>
          </button>
          <button
            onClick={(event) => {event.stopPropagation(); removeRecentSearchOnClick(term)}}>
              X
          </button>
          <br/><br/>
        </div>
      )
    })
  }

  // console.log("displaySettings: ", displaySettings);
  console.log("dividendsData")
  console.log(dividendsData)

  return (
    <div className="ui container" style={{marginTop: '10px'}}>
      <SearchBar term={term} onTermUpdate={onTermUpdate} />
      {renderRecentSearches()}

      <div className="ui segment">
        {renderMainContent()}
      </div>
    </div>
  )
}


const mapStateToProps = state => {
  return { userId: state.auth.userId };
};

export default connect(
  mapStateToProps
)(SearchPage);

// export default SearchPage;

the display main component:

import React, {useState} from 'react';

import MainDividendResultsDisplay from './MainDividendResultsDisplay';
import DividendYieldChangeDisplay from './DividendYieldChangeDisplay';
import AllDividendsDisplay from './AllDividendsDisplay';


const DividendResultsDisplay = (props) => {

  const [descriptionVisible, setDescriptionVisible] = useState(false);

  const toggleDescription = (event) => {
    setDescriptionVisible(!descriptionVisible);
  }

  const renderDescription = () => {
    if (descriptionVisible) {
      return (
        <p style={{cursor: 'pointer'}} onClick={toggleDescription}>{props.dividendsData.summary}</p>
      )
    } else {
      return (
        <a style={{cursor: 'pointer'}} onClick={toggleDescription}>Description <div className="ui icon caret up"></div></a>
      )
    }
  }

  console.log("data object in DividendResultsDisplay")
  console.log(props.dividendsData)

  console.log("current price DividendResultsDisplay")
  console.log(props.dividendsData.current_price)

  const mainInfo = (
    <MainDividendResultsDisplay
      current_price={props.dividendsData.current_price}
      recent_dividend_rate={props.dividendsData.recent_dividend_rate}
      current_yield={props.dividendsData.current_yield}
    />
  )

  const yieldChange = (
    <DividendYieldChangeDisplay
      displaySettings={props.displaySettings}
      toggleYieldChange={props.toggleYieldChange}
      dividend_change_1_year={props.dividendsData.dividend_change_1_year}
      dividend_change_3_year={props.dividendsData.dividend_change_3_year}
      dividend_change_5_year={props.dividendsData.dividend_change_5_year}
      dividend_change_10_year={props.dividendsData.dividend_change_10_year}
    />
  )

  const allDividends = (
    <AllDividendsDisplay
      displaySettings={props.displaySettings}
      toggleAllDividends={props.toggleAllDividends}
      all_dividends={props.dividendsData.all_dividends}
      dividends_years_back={props.dividends_years_back}
      dividendsYearsBackOnChange={props.dividendsYearsBackOnChange}/>
  )

  console.log("data in DividendResultsDisplay")
  console.log(props.data)

  // console.log("props.data.all_dividends in DividendResultsDisplay")
  // console.log(props.data.all_dividends)


  return (
    <div>
      <h3>{props.data.name}</h3>
      <h4>{props.data.sector}</h4>
      {renderDescription()}
      <br/><br/>
      {mainInfo}
      <br/>
      {yieldChange}
      <br/>
      {allDividends}
    </div>
  )
};

export default DividendResultsDisplay;

one of the display subcomponents also getting undefined value to its prop:

import React from 'react';

import './DividendResults.css';


const AllDividendsDisplay = (props) => {

  console.log("props in AllDividendsDisplay")
  console.log(props)
  
  let dividends_rows = null;
  if (props.all_dividends) {
    dividends_rows = props.all_dividends.map((dividends_object) => {
      return (
        <tr key={dividends_object.date}>
          <td>{dividends_object.date}</td>
          <td>{dividends_object.amount}</td>
        </tr>
      )
    });
  }

  const allDividendsDisplaySetting = props.displaySettings.find((dict) => dict.setting_name == 'showAllDividends');
  let mainDisplay = null;
  if (allDividendsDisplaySetting.visible) {
    mainDisplay = (
      <table className="ui celled table">
        <thead>
          <tr>
            <th>Date</th>
            <th>Amount</th>
          </tr>
        </thead>
        <tbody>
          {dividends_rows}
        </tbody>
      </table>
    )
  }

  return (
    <div>
      <h3>The dividends for the last &nbsp;
          <input
            type="text"
            style={{width: '48px'}}
            value={props.dividends_years_back}
            onChange={(e) => props.dividendsYearsBackOnChange(e.target.value)}
            /> years:
            <div
              style={{cursor: 'pointer'}}
              onClick={props.toggleAllDividends}>
                &nbsp;&nbsp;{allDividendsDisplaySetting.visible ? '-' : '+'}&nbsp;&nbsp;
            </div>
      </h3>
      {mainDisplay}
    </div>
  );

};


export default AllDividendsDisplay;

enter image description here

Data is successfully being loaded in the search now but it still shows up as undefined

enter image description here

all the data

enter image description here

the API endpoint on local works fine

enter image description here

.env file:

REACT_APP_HOSTNAME=localhost
REACT_APP_PROTOCOL=http
REACT_APP_PORT=8000

The issue can be seen here:

enter image description here

codyc4321
  • 9,014
  • 22
  • 92
  • 165
  • code be downloaded at https://github.com/codyc4321/dividends_ui. thank you for any help – codyc4321 Aug 22 '22 at 17:43
  • 1
    There is no prop `data` being passed to DividendResultsDisplay – windowsill Aug 22 '22 at 17:46
  • 1
    You must mean props.dividendsData.name – windowsill Aug 22 '22 at 17:49
  • o idk why i changed that. let me update – codyc4321 Aug 22 '22 at 17:52
  • please see new picture at bottom. Now the data is retrieved from the search but still shows up undefined in children – codyc4321 Aug 22 '22 at 17:56
  • as far as i can see the names of props are correct – codyc4321 Aug 22 '22 at 17:58
  • 1
    in your searchPage, axios on line 114 is not giving any response because your .get(dividends_ap_url, {}) is broken. so your setDividendsData is not working – verunar Aug 23 '22 at 14:16
  • but isn't the data showing up on the main SearchPage? I added a picture showing the data printing out in search page. Let me check the endpoint ty I will let u know – codyc4321 Aug 23 '22 at 14:36
  • 1
    what is the url meant to be? ill stick it in manually and see – verunar Aug 23 '22 at 14:44
  • 1
    or even better what are your environment variables? – verunar Aug 23 '22 at 14:50
  • I will add env to the text – codyc4321 Aug 23 '22 at 14:54
  • added .env file. let me see if the search is even running, if it is ill print out the URL – codyc4321 Aug 23 '22 at 14:58
  • you can also download the backend for comparison since I dont have mock data yet: https://github.com/codyc4321/stocks_backend – codyc4321 Aug 23 '22 at 15:01
  • I found the issue, the search URL is coming in as undefined, so the code completes runSearch and sets all the data to undefined. I have to go work for about 15 minutes I can try to solve it when I get back, if you have any ideas until then please let me know – codyc4321 Aug 23 '22 at 15:04
  • now the data is showing up on search page but being undefined in display components – codyc4321 Aug 23 '22 at 17:20
  • your comments helped me debug correctly! I had like 6 print statements but didn't have a clue what to do. I couldn't have fixed it without you guys – codyc4321 Aug 23 '22 at 19:43
  • 1
    Well done! Always follow the data up as far as possible and start with the top error. and from what you have written it looks like you are not using a version control system like GIT, this is a must for any developer it will help you immensely! Also you should write an answer below, even if youre replying to yourself, otherwise u get downvoted, we are already breaking rules by commenting so much – verunar Aug 23 '22 at 21:51
  • I provided the answer, thank you. I am using git but very poorly, I forgot to make a new branch when I added the earnings history and I broke my main – codyc4321 Aug 23 '22 at 22:10

1 Answers1

1

Using this new dataframe to add in the earnings history for each company caused a strange bug due to Not a Number result (a pandas datatype which means the value is missing). To fix this issue use pandas.isna(value) to check if its a nan pandas type:

https://towardsdatascience.com/5-methods-to-check-for-nan-values-in-in-python-3f21ddd17eed

def gather_earnings_objects(yahoo_obj):
    history = yahoo_obj.earnings_history
    row_count = 100
    earnings = []
    for i in range(row_count):
        try:
            data = {}
            # import ipdb; ipdb.set_trace()
            parsed_date = parse_earnings_history_date(history.iloc[i][2])
            data['date'] = parsed_date

            expected_value = history.iloc[i][3]
            if pandas.isna(expected_value):
                expected_value = 'no result'
            data['expected'] = expected_value

            actual_value = history.iloc[i][4]
            if pandas.isna(actual_value):
                actual_value = 'no result'
            data['actual'] = actual_value

            surprise = history.iloc[i][5]
            if pandas.isna(surprise):
                surprise = 'no result'
            data['surprise'] = surprise

            earnings.append(data)
        except IndexError:
            print("Less than 100 rows for this dataframe")
        except:
            print("Unknown error parsing earnings history dataframe")
    print(earnings)
    return earnings

if you allow nan not a number pandas datatype to go into your json.dumps(data) it will break the axios call in the front end by causing a string to be loaded into state instead of a javascript object which has the attributes I'm trying to read to the display page. This is because JSON doesn't know what to do with pandas nan datatype

codyc4321
  • 9,014
  • 22
  • 92
  • 165