Skip to content

Commit 1814db6

Browse files
Update exercise, add exercise and update the capstone
1 parent 27a03a2 commit 1814db6

21 files changed

+1006
-332
lines changed

src/api/DataApi.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default class DataApi {
1515
.then(res => pick(res, COMPANY_PROFILE_FIELDS));
1616
}
1717

18-
static async getComapnyFinancial(company) {
18+
static async getCompanyFinancial(company) {
1919
return DataApi.getFullCompanyProfile(company)
2020
.then(res => pick(res, COMPANY_FINANCIAL_FIELDS));
2121
}

src/capstone/Capstone.js

Lines changed: 55 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,85 +6,76 @@ import CompanyProfile from './CompanyProfile';
66
import CompanyFinancial from './CompanyFinancial';
77
import style from './solution/style';
88

9-
import DataApi from '../api/DataApi';
10-
119
const EMPTY_TICKER_MSG = 'Please type a stock ticker and click Search button.';
12-
const ERROR_MSG = 'Couldn\'t find the ticker. Please search for appropriate ticker like - AAPL for Apple, AMZN for Amazon.'
1310

11+
/**
12+
* 🏆
13+
* The goal of this capstone project is to bring together most of the
14+
* concepts you have learned in this tutorial together by building this feature.
15+
* The feature we are building is pretty straight forward. There's an input
16+
* field and a search button. When the user types in a valid stock ticker and
17+
* clicks Search button, it will display the company profile as well as
18+
* company financial for the given stock ticker selected.
19+
*/
1420
class Capstone extends Component {
1521
constructor(props) {
1622
super(props);
23+
//💡 Always initialize the state with whatever you think is appropriate default value
1724
this.state = {
18-
company: '',
19-
companyFinancial: {},
20-
companyProfile: {}
25+
stockTicker: ''
2126
}
2227

23-
this.handleInputChange = this.handleInputChange.bind(this);
24-
this.handleSearch = this.handleSearch.bind(this);
25-
this.updateCompanyFinancial = this.updateCompanyFinancial.bind(this);
26-
this.updateCompanyProfile = this.updateCompanyProfile.bind(this);
27-
this.handleError = this.handleError.bind(this);
28-
}
29-
30-
handleInputChange({ target: { value } }) {
31-
this.setState({
32-
company: value,
33-
errorMsg: '',
34-
companyFinancial: '',
35-
companyProfile: ''
36-
})
28+
//✏️ Bind the handleSearch function below to appropriate `this`
29+
// this.handleSearch = this.handleSearch.bind(this);
3730
}
3831

39-
handleSearch() {
40-
DataApi.getCompanyProfile(this.state.company)
41-
.then(this.updateCompanyProfile)
42-
.catch(this.handleError);
43-
44-
DataApi.getComapnyFinancial(this.state.company)
45-
.then(this.updateCompanyFinancial)
46-
.catch(this.handleError);
47-
}
48-
49-
handleError() {
50-
this.setState({ errorMsg: ERROR_MSG })
51-
}
52-
53-
updateCompanyProfile(companyProfile) {
54-
this.setState({ companyProfile })
55-
}
56-
57-
updateCompanyFinancial(companyFinancial) {
58-
this.setState({ companyFinancial })
32+
handleSearch(stockTicker) {
33+
/**
34+
* ✏️
35+
* When this method is called, it will be given a stockTicker
36+
* as an argument. You need to call setState to set the stockTicker
37+
* state to the value provided to this function
38+
*/
5939
}
6040

6141
render() {
62-
const CompanyProfileComponent = (
63-
!isEmpty(this.state.companyProfile) &&
64-
!this.state.errorMsg &&
65-
<CompanyProfile companyProfile={this.state.companyProfile} />
66-
);
67-
const CompanyFinancialComponent = (
68-
!isEmpty(this.state.companyProfile) &&
69-
!this.state.errorMsg &&
70-
<CompanyFinancial companyFinancial={this.state.companyFinancial} />
71-
);
72-
const ErrorMessage = (
73-
this.state.errorMsg &&
74-
<div>{this.state.errorMsg}</div>
75-
)
76-
const EmptyTickerMessage = (
77-
isEmpty(this.state.companyProfile) &&
78-
isEmpty(this.state.companyFinancial) &&
79-
<div>{EMPTY_TICKER_MSG}</div>
80-
)
42+
/**
43+
* ✏️
44+
* We want to render a message if no stock ticker has been searched.
45+
* We want to conditionally render the EMPTY_TICKER_MSG that's already provided
46+
* 🧭 There's an isEmpty function that's already imported. Use that
47+
* to check if the stockTicker state is empty
48+
* Like: isEmpty(this.state.stockTicker)
49+
* 🧭 If it is empty assign <div>{EMPTY_TICKER_MSG}</div> to EmptyTickerMessage
50+
* 🧭 If the stockTicker is not empty assign null to EmptyTickerMessage
51+
* You can either use ternery operator -
52+
* const a = isEmpty(b) ? c : null;
53+
* OR you can use '&&' operator -
54+
* const a = isEmpty(b) && c;
55+
* Both ways you are telling EmptyTickerMessage to display the div with the
56+
* error message only when the stockTicker state is empty
57+
*/
58+
const EmptyTickerMessage = null;
59+
60+
/**
61+
* 💡 Some things to note below inside the return:
62+
* 1. We are passing `handleSearch` function to `Search` component as `onSearch` props.
63+
* `Search` component will execute this props when the user clicks Search button.
64+
* and it will pass the current value on the input field as an argument to this function.
65+
* Remeber above we we updated the state when this function is called
66+
* 2. We are passing `stockTicker` props to both `CompanyProfile` and
67+
* `CompanyFinancial` components. These components should use the `stockTicker`
68+
* props to fetch the appropriate data from the API provided and render it.
69+
* 3. We have a line like {EmptyTickerMessage}. It's the constant we defined above.
70+
* We assigned a JSX to a constant and rendered it here inside curly braces.
71+
* This is a cool thing about JSX. They can be passed around like any object or function or data.
72+
*/
8173
return (
8274
<div style={style.container}>
83-
<Search searchValue={this.state.company} onInputChange={this.handleInputChange} onSearch={this.handleSearch} />
84-
{CompanyProfileComponent}
85-
{CompanyFinancialComponent}
86-
{this.state.errorMsg && ErrorMessage}
87-
{!this.state.company && EmptyTickerMessage}
75+
<Search onSearch={this.handleSearch} />
76+
<CompanyProfile stockTicker={this.state.stockTicker} />
77+
<CompanyFinancial stockTicker={this.state.stockTicker} />
78+
{EmptyTickerMessage}
8879
</div>
8980
);
9081
}

src/capstone/CompanyFinancial.jsx

Lines changed: 179 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,189 @@
11
import React, { Component } from 'react';
2+
import isEmpty from 'lodash/isEmpty';
3+
4+
import DataAPI from '../api/DataApi';
25
import style from './solution/style';
36

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+
*/
417
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+
5132
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;
26153

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+
}
27179
</div>
180+
181+
</div>
182+
)
183+
return (
184+
<div style={style.financialContainer}>
185+
{ErrorMsg}
186+
{CompanyFinancialSection}
28187
</div>
29188
);
30189
}

0 commit comments

Comments
 (0)