|
1 | 1 | import React, { Component } from 'react';
|
| 2 | +import isEmpty from 'lodash/isEmpty'; |
| 3 | + |
| 4 | +import DataAPI from '../api/DataApi'; |
2 | 5 | import style from './solution/style';
|
3 | 6 |
|
| 7 | +const ERROR_MSG = `Error when fetching company financial data. Please check if you entered valid stock ticker`; |
| 8 | + |
| 9 | +/** |
| 10 | + * 🏆 |
| 11 | + * This component is responsible for displaying the financial of the stock ticker |
| 12 | + * provided to it as a `props` by it's parent. |
| 13 | + * Here we will see how to fetch data using some lifecycle methods and also potential |
| 14 | + * way to handle an error if it were to arise. We will conditionally render different |
| 15 | + * things based on some state values. |
| 16 | + */ |
4 | 17 | class CompanyFinancial extends Component {
|
| 18 | + constructor(props) { |
| 19 | + super(props); |
| 20 | + /** |
| 21 | + * 💡 |
| 22 | + * Initialized the state with empty values |
| 23 | + */ |
| 24 | + this.state = { |
| 25 | + companyFinancial: {}, |
| 26 | + errorMsg: '' |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * 💡 |
| 31 | + * These functions are already bound for you with `this` |
| 32 | + */ |
| 33 | + this.updateCompanyFinancial = this.updateCompanyFinancial.bind(this); |
| 34 | + this.handleError = this.handleError.bind(this); |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * 💡 |
| 39 | + * This is a lifecycle method. React will invoke this lifecyle method |
| 40 | + * after the component is mounted to the DOM for the first time |
| 41 | + */ |
| 42 | + componentDidMount() { |
| 43 | + /** |
| 44 | + * ✏️ |
| 45 | + * You need to fetch the financial data here. |
| 46 | + * There's a fetchCompanyFinancial function already defined below |
| 47 | + * that will fetch the data from the given API |
| 48 | + * You just need to invoke it here |
| 49 | + */ |
| 50 | + } |
| 51 | + |
| 52 | + /* |
| 53 | + * 💡 |
| 54 | + * This is another lifecycle method. React will invoke this lifecyle method |
| 55 | + * after the component is updated. Remember the component will be updated |
| 56 | + * every time either the props changes or the state changes for this component |
| 57 | + */ |
| 58 | + componentDidUpdate(prevProps, prevState) { |
| 59 | + /** |
| 60 | + * ✏️ |
| 61 | + * You need to call the fetchCompanyFinancial method to fetch the |
| 62 | + * comanyFinancial data when the parent passes you a different stockTicker |
| 63 | + * 🧭 Remember to check if the stockTicker props changed before calling the |
| 64 | + * fetchCompanyFinancial method though. You DON'T want to fetch the data |
| 65 | + * if the stockTicker hasn't changed. If you don't check whether props |
| 66 | + * changed your component will go in an infinite loop - it will be |
| 67 | + * fetching the same data over and over again. |
| 68 | + * This lifecycle method will be given multiple arguments. |
| 69 | + * First argument is the value of props before this component updated |
| 70 | + * Second argument is the value of the state before this component updated |
| 71 | + * In our case we just want to check props to see if value for stockTicker |
| 72 | + * changed and the way to do this is: |
| 73 | + * if (this.props.stockTicker !== prevProps.stockTicker) { |
| 74 | + * //Fetch data here only if the current props is not same as previous props |
| 75 | + * } |
| 76 | + */ |
| 77 | + } |
| 78 | + |
| 79 | + /** |
| 80 | + * 💡 |
| 81 | + * This is a method to fetch the company financial data from the API. |
| 82 | + * Couple things to note here: |
| 83 | + * 1. We are updating the errorMsg state to empty string. This is jus to |
| 84 | + * reset any error message we might have from previous search |
| 85 | + * 2. We invoke the API only when the stockTicker is truthy. No point in |
| 86 | + * calling the API if we don't have any value for stockTicker |
| 87 | + * 3. We use the data received from the API to update companyFinancial state |
| 88 | + * (look below for updateCompanyFinancial function implementation) |
| 89 | + * 4. We catch any Error we get from the API and call handleError method |
| 90 | + * to handle the error. |
| 91 | + */ |
| 92 | + fetchCompanyFinancial() { |
| 93 | + const { stockTicker } = this.props; |
| 94 | + this.updateErrorMsg(''); |
| 95 | + if (stockTicker) { |
| 96 | + DataAPI.getCompanyFinancial(this.props.stockTicker) |
| 97 | + .then(this.updateCompanyFinancial) |
| 98 | + .catch(this.handleError) |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + /** |
| 103 | + * 💡 |
| 104 | + * Updates the companyFinancial state with the argument provided |
| 105 | + */ |
| 106 | + updateCompanyFinancial(companyFinancial) { |
| 107 | + this.setState({ companyFinancial }) |
| 108 | + } |
| 109 | + |
| 110 | + /** |
| 111 | + * 💡 |
| 112 | + * Updates the errorMsg state with the argument provided |
| 113 | + */ |
| 114 | + updateErrorMsg(errorMsg) { |
| 115 | + this.setState({ errorMsg }); |
| 116 | + } |
| 117 | + |
| 118 | + /** |
| 119 | + * 💡 |
| 120 | + * This is used to handle any error that we might get when we call the API |
| 121 | + * The API throws an error when for example the stockTicker provided |
| 122 | + * is not a valid stockTicker. |
| 123 | + */ |
| 124 | + handleError(error) { |
| 125 | + //This sets the state `errorMsg` with the ERROR_MSG defined at the very top |
| 126 | + this.updateErrorMsg(ERROR_MSG); |
| 127 | + //Since there's an error we want to reset the companyFincial state |
| 128 | + //with empty object. We don't want to display stale data |
| 129 | + this.updateCompanyFinancial({}); |
| 130 | + } |
| 131 | + |
5 | 132 | render() {
|
6 |
| - const { companyFinancial } = this.props; |
7 |
| - return ( |
8 |
| - <div style={style.financialContainer}> |
9 |
| - <div> |
10 |
| - <div style={style.financialTitle}>Financial</div> |
11 |
| - <div style={style.financialContent}> |
12 |
| - { |
13 |
| - Object.keys(companyFinancial) |
14 |
| - .map(prop => { |
15 |
| - return <div key={prop} style={style.financialMetricRow}> |
16 |
| - <div style={style.financialMetric}> |
17 |
| - {prop} |
18 |
| - </div> |
19 |
| - <div> |
20 |
| - {companyFinancial[prop]} |
21 |
| - </div> |
22 |
| - </div> |
23 |
| - }) |
24 |
| - } |
25 |
| - </div> |
| 133 | + const { companyFinancial } = this.state; |
| 134 | + /** |
| 135 | + * ✏️ |
| 136 | + * We want to render an error message if the API returns some error. |
| 137 | + * We want to check if we have `errorMsg` state is not empty and |
| 138 | + * if it's not render the message inside a div |
| 139 | + * 🧭 There's an `isEmpty` function that's already imported. Use that |
| 140 | + * to check if the `errorMsg` state is empty |
| 141 | + * Like: isEmpty(this.state.errorMsg) |
| 142 | + * 🧭 If it is empty assign null to ErrorMsg |
| 143 | + * 🧭 If the errorMsg is not empty assign <div>{this.state.errorMsg}</div> |
| 144 | + * to the ErrorMsg constant. |
| 145 | + * You can either use ternery operator - |
| 146 | + * const a = isEmpty(b) ? c : null; |
| 147 | + * OR you can use '&&' operator - |
| 148 | + * const a = isEmpty(b) && c; |
| 149 | + * Either ways you are telling ErrorMsg to display the div with the |
| 150 | + * error message only when the `erroMsg` state is not empty |
| 151 | + */ |
| 152 | + const ErrorMsg = null; |
26 | 153 |
|
| 154 | + /** |
| 155 | + * 💡 |
| 156 | + * Here we are doing same thing as the ErrorMsg above |
| 157 | + * Instead of checking for `errorMsg` we are checking for `companyFinancial` |
| 158 | + * state. We are displaying the `div` only if the `companyFinancial` |
| 159 | + * state is not empty. |
| 160 | + */ |
| 161 | + const CompanyFinancialSection = ( |
| 162 | + !isEmpty(this.state.companyFinancial) && |
| 163 | + <div> |
| 164 | + <div style={style.financialTitle}>Financial</div> |
| 165 | + <div style={style.financialContent}> |
| 166 | + { |
| 167 | + Object.keys(companyFinancial) |
| 168 | + .map(prop => { |
| 169 | + return <div key={prop} style={style.financialMetricRow}> |
| 170 | + <div style={style.financialMetric}> |
| 171 | + {prop} |
| 172 | + </div> |
| 173 | + <div> |
| 174 | + {companyFinancial[prop]} |
| 175 | + </div> |
| 176 | + </div> |
| 177 | + }) |
| 178 | + } |
27 | 179 | </div>
|
| 180 | + |
| 181 | + </div> |
| 182 | + ) |
| 183 | + return ( |
| 184 | + <div style={style.financialContainer}> |
| 185 | + {ErrorMsg} |
| 186 | + {CompanyFinancialSection} |
28 | 187 | </div>
|
29 | 188 | );
|
30 | 189 | }
|
|
0 commit comments