├── .gitignore ├── .gitpod.yml ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── Learn_React_App.gif ├── favicon.ico ├── index.html └── manifest.json ├── scripts └── processMarkdown.js ├── src ├── App.js ├── FileReader.js ├── api │ ├── CompanyDataGenerator.js │ └── DataApi.js ├── capstone │ ├── Capstone.js │ ├── CompanyFinancial.jsx │ ├── CompanyProfile.jsx │ ├── Search.jsx │ └── solution │ │ ├── Capstone.js │ │ ├── CompanyFinancial.jsx │ │ ├── CompanyProfile.jsx │ │ ├── Search.jsx │ │ └── style.js ├── exercise │ ├── 01-HelloWorld.js │ ├── 01-HelloWorld_ts.tsx │ ├── 02-IntroToJSX.js │ ├── 02-IntroToJSX_ts.tsx │ ├── 03-PowerOfJSX.js │ ├── 03-PowerOfJSX_ts.tsx │ ├── 04-Props.js │ ├── 04-Props_ts.tsx │ ├── 05-State.js │ ├── 05-State_ts.tsx │ ├── 06-LifecycleMethods.js │ ├── 06-LifecycleMethods_ts.tsx │ ├── 07-HandlingEvents.js │ ├── 07-HandlingEvents_ts.tsx │ ├── 08-ComposingComponents.js │ ├── 08-ComposingComponents_ts.tsx │ └── solution │ │ ├── 01-HelloWorld-solution.js │ │ ├── 02-IntroToJSX-solution.js │ │ ├── 03-PowerOfJSX-solution.js │ │ ├── 04-Props-solution.js │ │ ├── 05-State-solution.js │ │ ├── 06-LifecycleMethods-solution.js │ │ ├── 07-HandlingEvents-solution.js │ │ └── 08-ComposingComponents-solution.js ├── index.css ├── index.js ├── react-app-env.d.ts ├── setup │ ├── AppShell.js │ ├── Exercise.jsx │ ├── Home.js │ ├── Markdown.jsx │ ├── ScrollToTop.js │ ├── Solution.jsx │ ├── Tutorial.jsx │ ├── TutorialMetadataApi.js │ ├── TutorialNavigation.jsx │ ├── renderer │ │ ├── CodeBlock.css │ │ ├── CodeBlock.js │ │ └── HyperLink.js │ └── tutorialMetadata.js └── tutorial │ ├── 00-ReactIntroduction.md │ ├── 01-HelloWorld.md │ ├── 02-IntroToJSX.md │ ├── 03-PowerOfJSX.md │ ├── 04-Props.md │ ├── 05-State.md │ ├── 06-LifecycleMethods.md │ ├── 07-HandlingEvents.md │ ├── 08-ComposingComponents.md │ ├── 09-Capstone.md │ └── 10-Conclusion.md ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | #markdown-build 15 | /src/tutorial/build 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 3000 3 | onOpen: open-preview 4 | tasks: 5 | - before: export DANGEROUSLY_DISABLE_HOST_CHECK=true 6 | init: yarn install 7 | command: yarn start 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - ~/.npm 6 | notifications: 7 | email: false 8 | node_js: '8' 9 | install: npm install 10 | branches: 11 | only: master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ganesh Bhattarai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Welcome to Learn React App!

3 | 4 | 5 | [![MIT License][license-badge]][license] 6 | [![Last commit][github-last-commit-badge]][github-commit] 7 | 8 | [![Github Contributors][github-contributors-badge]][github-contributors] 9 | [![Watch on GitHub][github-watch-badge]][github-watch] 10 | [![Star on GitHub][github-star-badge]][github-star] 11 | 12 |
13 | 14 |
15 | 16 | The goal of this tutorial is to quickly get you off the ground with `React` concepts. This tutorial has hands-on exercises which I consider to be the most important part of this tutorial. 17 | 18 | The way this tutorial works is that first, you have to checkout this project on your computer, and run the application locally. Then open the application on a browser and you can go through the tutorial as you like. 19 | 20 | ![Learn React App](./public/Learn_React_App.gif "Learn React App") 21 | 22 | 23 | ## Checkout 24 | 25 | Checkout the project to your computer using `git`: 26 | 27 | ``` 28 | git clone https://github.com/tyroprogrammer/learn-react-app.git 29 | ``` 30 | 31 | ## Environment Setup 32 | 33 | You can either use `npm` or `yarn` to run this application. Please pick one and follow below instructions. 34 | 35 | *If you want to use `yarn` and don't have `yarn` installed on your local machine please execute below command to install `yarn`:* 36 | 37 | ``` 38 | npm install -g yarn 39 | ``` 40 | 41 | ### Installing Dependencies 42 | 43 | On the root directory of the project please execute **either one** of the below commands to install all the project dependencies. You don't have to run both commands, just pick one. 44 | 45 | ``` 46 | yarn install 47 | ``` 48 | 49 | **OR** 50 | 51 | ``` 52 | npm install 53 | ``` 54 | 55 | ### Starting application 56 | 57 | On the root directory of the project please execute either one of the below commands to start the tutorial application: 58 | 59 | ``` 60 | yarn start 61 | ``` 62 | 63 | **OR** 64 | ``` 65 | npm start 66 | ``` 67 | 68 | After this is complete, the application will be deployed on port `3000`. Open a browser and navigate to `localhost:3000`. 69 | 70 | ----- 71 | 72 | ## Online Tutorial 73 | 74 | You can also run this tutorial in Gitpod, a free online dev environment for GitHub: 75 | 76 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/tyroprogrammer/learn-react-app/blob/master/src/exercise/01-HelloWorld.js) 77 | 78 | ----- 79 | 80 | ## Following the tutorial 81 | 82 | Tutorials on this application are fairly straightforward to follow. Each tutorial has one or more exercises. You'll see once you are in the tutorial. 83 | 84 | The exercise panel has split view. The left-hand side of the screen has your solution rendered and the right-hand side of the screen has the target solution. 85 | Right above the exercise panel, you'll see the location of exercise files. 86 | 87 | Please open the exercise file on your favorite editor (VS Code, Atom, Sublime, IntelliJ etc.) and start making changes by following the instructions. Exercise files are heavily commented. Read through the comments and you should be able to write up the solution. If you have any confusion you can refer to the solution file for that exercise. Every time you make changes to the exercise file and save it, the browser will reload automatically reflecting your changes. 88 | 89 | ### Exercise Comment Guide 90 | 91 | Most comments in the exercise files start with one of the below signs. This is to help you understand what you should do to the code immediately following these comments. 92 | 93 | 🏆 - **Trophy** - Describes the overall goal of the exercise. You can find this at the top of the exercise file. 94 | 95 | 💡 - **Light Bulb** - General information regarding the code immediately following this comment. You might find it throughout the code. No action is required on your part, just read them. 96 | 97 | ✏️ - **Pencil** - You are supposed to edit the code immediately following this comment. It is followed by a description of the change that you need to do. 98 | 99 | 🧭 - **Compass** - When the description of change is not enough, the compass will give you more direction. You will find it alongside the pencil when more elaborate instruction is deemed necessary. 100 | 101 | 🚨 - **Alarm** - This means danger. Read the comment carefully. Usually, it's used to say you shouldn't change the code immediately following this. It will create havoc. 102 | 103 | ### FAQ 104 | 105 |
106 | Do I need to install `yarn` or can I use `npm`? 107 |

You don't really need yarn. Just use npm if you like.

108 |
109 | 110 |
111 | Which browser should I use? 112 |

This tutorial has been tested in Chrome only so I highly recommend you use Chrome.

113 |
114 | 115 |
116 | Which code editor should I use for exercise? 117 |

Anything really (Sublime, Atom, VS Code, IntelliJ) - its your preference.

118 |
119 | 120 |
121 | I accidentally deleted something in an exercise that I shouldn't have. What should I do? 122 |

The easiest way is to just revert back to the previous version on your editor. If you want to start anew, then just checkout that particular file from GitHub again using something like:

git checkout HEAD --  exercise/01-helloWorld.js
123 |

124 |
125 | 126 | ## Contribution 127 | 128 | If you went through the exercise and saw some inconsistencies or if you have an idea to make the overall tutorial better please feel free to open a PR. 129 | 130 | 131 | 132 | 133 | [license-badge]: https://img.shields.io/github/license/tyroprogrammer/learn-react-app.svg?style=flat 134 | [license]: https://github.com/tyroprogrammer/learn-react-app/blob/master/LICENSE 135 | [github-watch-badge]: https://img.shields.io/github/watchers/tyroprogrammer/learn-react-app.svg?style=social 136 | [github-watch]: https://github.com/tyroprogrammer/learn-react-app/watchers 137 | [github-star-badge]: https://img.shields.io/github/stars/tyroprogrammer/learn-react-app.svg?style=social 138 | [github-star]: https://github.com/tyroprogrammer/learn-react-app/stargazers 139 | [github-last-commit-badge]: https://img.shields.io/github/last-commit/tyroprogrammer/learn-react-app.svg?style=flat 140 | [github-commit]: https://github.com/tyroprogrammer/learn-react-app/commits/master 141 | [github-contributors-badge]: https://img.shields.io/github/contributors/tyroprogrammer/learn-react-app.svg?style=social 142 | [github-contributors]: https://github.com/tyroprogrammer/learn-react-app/graphs/contributors 143 | 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^3.9.3", 7 | "@material-ui/icons": "^3.0.2", 8 | "@types/jest": "^24.0.12", 9 | "@types/node": "^12.0.0", 10 | "@types/react": "^16.8.17", 11 | "@types/react-dom": "^16.8.4", 12 | "classnames": "^2.2.6", 13 | "highlight.js": "^9.15.8", 14 | "lodash": "^4.17.11", 15 | "prop-types": "^15.6.2", 16 | "react": "^16.8.6", 17 | "react-addons-shallow-compare": "^15.6.2", 18 | "react-dom": "^16.8.6", 19 | "react-lowlight": "^2.0.0", 20 | "react-markdown": "^4.0.8", 21 | "react-router": "^4.3.1", 22 | "react-router-dom": "^4.3.1", 23 | "react-scripts": "^3.3.0", 24 | "styled-components": "^4.3.1", 25 | "typescript": "^3.4.5" 26 | }, 27 | "scripts": { 28 | "start": "npm run build:markdown && react-scripts start", 29 | "start:ts": "npm run build:markdown && REACT_APP_TS=true react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test --passWithNoTests", 32 | "eject": "react-scripts eject", 33 | "build:markdown": "node scripts/processMarkdown" 34 | }, 35 | "eslintConfig": { 36 | "extends": "react-app" 37 | }, 38 | "browserslist": [ 39 | ">0.2%", 40 | "not dead", 41 | "not ie <= 11", 42 | "not op_mini all" 43 | ], 44 | "devDependencies": { 45 | "chalk": "^2.4.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/Learn_React_App.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyroprogrammer/learn-react-app/a41485ee612d35f32fca56c2d2de41d2092fc026/public/Learn_React_App.gif -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyroprogrammer/learn-react-app/a41485ee612d35f32fca56c2d2de41d2092fc026/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | Learn React App 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /scripts/processMarkdown.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var chalk = require('chalk'); 4 | 5 | const TUTORIAL_PATH = 'src/tutorial'; 6 | const TUTORIAL_BUILD_PATH = 'src/tutorial/build'; 7 | const MARKDOWN_FILE_EXTENSION = 'md'; 8 | 9 | function checkBuildFolderExist(){ 10 | if (!fs.existsSync(TUTORIAL_BUILD_PATH)) { 11 | console.log(chalk.magenta(`Creating build directory`)); 12 | fs.mkdirSync(TUTORIAL_BUILD_PATH); 13 | } 14 | } 15 | 16 | function cleanBuildFolder(){ 17 | console.log(chalk.yellow(`Cleaning up build directory`)); 18 | fs.readdir(TUTORIAL_BUILD_PATH, function (err, items) { 19 | items.forEach(function(file) { 20 | var filePath = path.join(TUTORIAL_BUILD_PATH, file); 21 | fs.unlink(filePath, function(err) { 22 | if (err) { 23 | console.log(chalk.red(`Error when deleting file %s`), filePath); 24 | throw new Error(err); 25 | } 26 | }) 27 | }) 28 | }); 29 | } 30 | 31 | 32 | function createJSFilesFromMarkdown(){ 33 | console.log(chalk.green(`Building tutorial files from markdowns`)); 34 | fs.readdir(TUTORIAL_PATH, function(err, items) { 35 | items.forEach(function(file) { 36 | var filePath = path.join(TUTORIAL_PATH, file); 37 | fs.stat(filePath, function(err, stats) { 38 | if (stats.isFile() && isFileOfExtension(file, MARKDOWN_FILE_EXTENSION)) { 39 | fs.readFile(filePath, 'utf-8', function(err, data) { 40 | const newFileName = getFileNameWithoutExtension(file) + '.js'; 41 | const newFilePath = path.join(TUTORIAL_BUILD_PATH, newFileName); 42 | const dataToWrite = `export default \` ${data.replace(/`/g, '\\\`')} \``; 43 | fs.writeFile(newFilePath, dataToWrite, function(err) { 44 | if (err) { 45 | console.log(chalk.red(`Error writing file - %s`), newFilePath); 46 | throw new Error(err); 47 | } 48 | }) 49 | }) 50 | } 51 | }) 52 | }) 53 | }); 54 | } 55 | 56 | function getFileNameWithoutExtension(filename) { 57 | return filename.split('.').slice(0, -1).join('.'); 58 | } 59 | 60 | function isFileOfExtension(filename, extension) { 61 | return filename.split('.').slice(-1)[0] === extension; 62 | } 63 | 64 | checkBuildFolderExist(); 65 | cleanBuildFolder(); 66 | createJSFilesFromMarkdown(); 67 | 68 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | 4 | import Tutorial from './setup/Tutorial'; 5 | import Home from './setup/Home'; 6 | import ScrollToTop from './setup/ScrollToTop'; 7 | 8 | class App extends Component { 9 | render() { 10 | return ( 11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 | ); 25 | } 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /src/FileReader.js: -------------------------------------------------------------------------------- 1 | class FileReader { 2 | readFile(fileLocation) { 3 | let _fileLocation = fileLocation.replace('src', '.').replace('.js', ''); 4 | 5 | if (fileLocation.includes("exercise") && !fileLocation.includes("solution") && process.env.REACT_APP_TS === "true") { 6 | _fileLocation = fileLocation.replace('src', '.').replace('.js', '_ts'); 7 | } 8 | 9 | return import(`${_fileLocation}`) 10 | .then(data => data.default); 11 | } 12 | } 13 | 14 | export default new FileReader(); 15 | -------------------------------------------------------------------------------- /src/api/CompanyDataGenerator.js: -------------------------------------------------------------------------------- 1 | import { sample } from 'lodash'; 2 | 3 | const PRICE = { MIN: 25, MAX: 250 }; 4 | const BETA = { MIN: 1, MAX: 2 }; 5 | const VOLAVG = { MIN: 10000, MAX: 1000000 }; 6 | const MKTCAP = { MIN: 10000000, MAX: 50000000000 }; 7 | const LASTDIV_PERC = { MIN: 0, MAX: 0.03 }; 8 | const RANGESPREAD_PERC = { MIN: 0.02, MAX: 0.05 }; 9 | const CHANGESPERC = { MIN: 0.01, MAX: 0.05 }; 10 | 11 | const SECTOR_INDUSTRY = { 12 | 'Technology': ['Computer Hardware', 'Online Media', 'SemiConductor', 'Application Software'], 13 | 'Consumer': ['Restaurant', 'Utilities', 'Retail', 'Entertainment', 'Apparel'], 14 | 'Health Care': ['Medical Device'], 15 | 'Industrial': ['Airlines', 'Manufacturing'], 16 | 'Financial Services': ['Brokers & Exchanges', 'Banks'] 17 | }; 18 | 19 | const EXCHANGE = ['NASDAQ', 'NYSE']; 20 | 21 | class CompanyDataGenerator { 22 | constructor() { 23 | 24 | } 25 | 26 | getPrice() { 27 | return getRandomNumberBetween(PRICE.MIN, PRICE.MAX); 28 | } 29 | 30 | getBeta() { 31 | return getRandomNumberBetween(BETA.MIN, BETA.MAX, true); 32 | } 33 | 34 | getVolAvg() { 35 | return `${getRandomNumberBetween(VOLAVG.MIN, VOLAVG.MAX).toLocaleString('US')}`; 36 | } 37 | 38 | getMktCap() { 39 | return `$${getRandomNumberBetween(MKTCAP.MIN, MKTCAP.MAX).toLocaleString('US')}`; 40 | } 41 | 42 | getLastDiv(price) { 43 | return (price * getRandomNumberBetween(LASTDIV_PERC.MIN, LASTDIV_PERC.MAX, true)).toFixed(2); 44 | } 45 | 46 | getRange(price) { 47 | return `$${(price - price * RANGESPREAD_PERC.MIN).toFixed(2)} 48 | - $${(price + price * RANGESPREAD_PERC.MAX).toFixed(2)}` 49 | } 50 | 51 | getChangePerc() { 52 | return getRandomNumberBetween(CHANGESPERC.MIN, CHANGESPERC.MAX, true); 53 | } 54 | 55 | getChange(price, changePerc) { 56 | return (price * changePerc).toFixed(2); 57 | } 58 | 59 | getCompanyFinancial() { 60 | const Price = this.getPrice(); 61 | const Beta = this.getBeta(); 62 | const VolAvg = this.getVolAvg(); 63 | const MktCap = this.getMktCap(); 64 | const LastDiv = this.getLastDiv(Price); 65 | const Range = this.getRange(Price); 66 | const ChangePerc = this.getChangePerc(); 67 | const Change = this.getChange(Price, ChangePerc); 68 | return { 69 | Price: `$${Price}`, 70 | Beta, 71 | VolAvg, 72 | MktCap, 73 | LastDiv, 74 | Range, 75 | ChangePerc: `${ChangePerc * 100}%`, 76 | Change 77 | } 78 | } 79 | 80 | getDescription(name, sector) { 81 | const randomYear = getRandomNumberBetween(5, 50); 82 | return `${name} is an excellent company in ${sector} sector for past ${randomYear} years producing outstanding results for it's shareholders.` 83 | } 84 | 85 | getExchange() { 86 | return sample(EXCHANGE); 87 | } 88 | 89 | getIndustry(sector) { 90 | return sample(SECTOR_INDUSTRY[sector]); 91 | } 92 | 93 | getSector() { 94 | return sample(Object.keys(SECTOR_INDUSTRY)); 95 | } 96 | 97 | getWebsite(ticker) { 98 | return `http://www.${ticker.toLowerCase()}.com` 99 | } 100 | 101 | getCompanyProfile(ticker) { 102 | const companyName = ticker.toUpperCase(); 103 | const exchange = this.getExchange(); 104 | const sector = this.getSector(); 105 | const industry = this.getIndustry(sector); 106 | const website = this.getWebsite(ticker); 107 | const description = this.getDescription(companyName, sector); 108 | return { 109 | companyName, 110 | description, 111 | exchange, 112 | industry, 113 | sector, 114 | website 115 | } 116 | } 117 | } 118 | 119 | function getRandomNumberBetween(min, max, floatResult) { 120 | if (floatResult) { 121 | return (Math.random() * (max - min) + min).toFixed(2); 122 | } 123 | return Math.floor(min + (max - min + 1) * Math.random()); 124 | } 125 | 126 | export default new CompanyDataGenerator(); -------------------------------------------------------------------------------- /src/api/DataApi.js: -------------------------------------------------------------------------------- 1 | import CompanyDataGenerator from './CompanyDataGenerator'; 2 | 3 | export default class DataApi { 4 | static async getCompanyProfile(company) { 5 | return new Promise((resolve, reject) => { 6 | if (!company || company.trim().length !== 3) { 7 | reject('Ticker cannot be empty and must be 3 character long'); 8 | } 9 | try { 10 | const result = CompanyDataGenerator.getCompanyProfile(company); 11 | resolve(result); 12 | } catch(e) { 13 | console.error(e); 14 | reject(e); 15 | } 16 | }) 17 | } 18 | 19 | static async getCompanyFinancial(company) { 20 | return new Promise((resolve, reject) => { 21 | if (!company || company.trim().length !== 3) { 22 | reject('Ticker cannot be empty and must be 3 character long'); 23 | } 24 | try { 25 | const result = CompanyDataGenerator.getCompanyFinancial(company); 26 | resolve(result); 27 | } catch(e) { 28 | console.error(e); 29 | reject(e); 30 | } 31 | }) 32 | } 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/capstone/Capstone.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import isEmpty from 'lodash/isEmpty'; 3 | 4 | import Search from './Search'; 5 | import CompanyProfile from './CompanyProfile'; 6 | import CompanyFinancial from './CompanyFinancial'; 7 | import style from './solution/style'; 8 | 9 | const EMPTY_TICKER_MSG = 'Please type a stock ticker and click Search button.'; 10 | 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 | */ 20 | class Capstone extends Component { 21 | constructor(props) { 22 | super(props); 23 | //💡 Always initialize the state with whatever you think is appropriate default value 24 | this.state = { 25 | stockTicker: '' 26 | } 27 | 28 | //✏️ Bind the handleSearch function below to appropriate `this` 29 | // this.handleSearch = this.handleSearch.bind(this); 30 | } 31 | 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 | */ 39 | } 40 | 41 | render() { 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
{EMPTY_TICKER_MSG}
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 | * Remember above 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 | */ 73 | return ( 74 |
75 | 76 | 77 | 78 | {EmptyTickerMessage} 79 |
80 | ); 81 | } 82 | } 83 | 84 | export default Capstone; -------------------------------------------------------------------------------- /src/capstone/CompanyFinancial.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import isEmpty from 'lodash/isEmpty'; 3 | 4 | import DataAPI from '../api/DataApi'; 5 | import style from './solution/style'; 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 | */ 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 | 132 | render() { 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
{this.state.errorMsg}
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; 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 |
164 |
Financial
165 |
166 | { 167 | Object.keys(companyFinancial) 168 | .map(prop => { 169 | return
170 |
171 | {prop} 172 |
173 |
174 | {companyFinancial[prop]} 175 |
176 |
177 | }) 178 | } 179 |
180 | 181 |
182 | ) 183 | return ( 184 |
185 | {ErrorMsg} 186 | {CompanyFinancialSection} 187 |
188 | ); 189 | } 190 | } 191 | 192 | export default CompanyFinancial; -------------------------------------------------------------------------------- /src/capstone/CompanyProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import isEmpty from 'lodash/isEmpty'; 3 | import DataAPI from '../api/DataApi'; 4 | import style from './solution/style'; 5 | 6 | const ERROR_MSG = `Error when fetching company profile data. Please check if you entered valid stock ticker`; 7 | 8 | /** 9 | * 🏆 10 | * This component is responsible for displaying the Compnay Profile of the stock ticker 11 | * provided to it as a `props` by it's parent. 12 | * Here we will see how to fetch data using some lifecycle methods and also potential 13 | * way to handle an error if it were to arise. We will conditionally render different 14 | * things based on some state values. 15 | */ 16 | class CompanyProfile extends Component { 17 | constructor(props) { 18 | super(props); 19 | /** 20 | * 💡 21 | * Initialized the state with empty values 22 | */ 23 | this.state = { 24 | companyProfile: {}, 25 | errorMsg: '' 26 | } 27 | 28 | /** 29 | * 💡 30 | * These functions are already bound for you with `this` 31 | */ 32 | this.updateCompanyProfile = this.updateCompanyProfile.bind(this); 33 | this.updateErrorMsg = this.updateErrorMsg.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 profile data here. 46 | * There's a fetchCompanyProfile 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 fetchCompanyProfile method to fetch the 62 | * comanyProfile data when the parent passes you a different stockTicker 63 | * 🧭 Remember to check if the stockTicker props changed before calling the 64 | * fetchCompanyProfile 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 profile 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 companyProfile state 88 | * (look below for updateCompanyProfile function implementation) 89 | * 4. We catch any Error we get from the API and call handleError method 90 | * to handle the error. 91 | */ 92 | fetchCompanyProfile() { 93 | const { stockTicker } = this.props; 94 | this.updateErrorMsg(''); 95 | if (stockTicker) { 96 | DataAPI.getCompanyProfile(this.props.stockTicker) 97 | .then(this.updateCompanyProfile) 98 | .catch(this.handleError) 99 | } 100 | } 101 | 102 | /** 103 | * 💡 104 | * Updates the companyProfile state with the argument provided 105 | */ 106 | updateCompanyProfile(companyProfile) { 107 | this.setState({ companyProfile }); 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 `companyProfile` state 128 | //with empty object. We don't want to display stale data 129 | this.updateCompanyProfile({}); 130 | } 131 | 132 | render() { 133 | const { companyProfile } = this.state; 134 | const { description, ...rest } = companyProfile; 135 | /** 136 | * ✏️ 137 | * We want to render an error message if the API returns some error. 138 | * We want to check if we have `errorMsg` state is not empty and 139 | * if it's not render the message inside a div 140 | * 🧭 There's an `isEmpty` function that's already imported. Use that 141 | * to check if the `errorMsg` state is empty 142 | * Like: isEmpty(this.state.errorMsg) 143 | * 🧭 If it is empty assign null to ErrorMsg 144 | * 🧭 If the errorMsg is not empty assign
{this.state.errorMsg}
145 | * to the ErrorMsg constant. 146 | * You can either use ternery operator - 147 | * const a = isEmpty(b) ? c : null; 148 | * OR you can use '&&' operator - 149 | * const a = isEmpty(b) && c; 150 | * Either ways you are telling ErrorMsg to display the div with the 151 | * error message only when the `erroMsg` state is not empty 152 | */ 153 | const ErrorMsg = null; 154 | 155 | /** 156 | * 💡 157 | * Here we are doing same thing as the ErrorMsg above 158 | * Instead of checking for `errorMsg` we are checking for `companyProfile` 159 | * state. We are displaying the `div` only if the `companyProfile` 160 | * state is not empty. 161 | */ 162 | 163 | const CompanyProfileSection = ( 164 | !isEmpty(this.state.companyProfile) && 165 |
166 |
167 | {description} 168 |
169 |
170 | { 171 | Object.keys(rest) 172 | .map(data => { 173 | return ( 174 |
175 |
{data.toUpperCase()}
176 |
{companyProfile[data]}
177 |
178 | ) 179 | }) 180 | } 181 |
182 |
183 | ) 184 | return ( 185 |
186 | {ErrorMsg} 187 | {CompanyProfileSection} 188 |
189 | ); 190 | } 191 | } 192 | 193 | export default CompanyProfile; -------------------------------------------------------------------------------- /src/capstone/Search.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import style from './solution/style'; 3 | 4 | /** 5 | * 🏆 6 | * This component houses the input and the Search button. 7 | * When the user types in something we handle the change event and 8 | * store the values typed in the input field to the state 9 | * When user clicks the Search button it will invoke a callback function 10 | * that was passed to this component as a props with the latest input value 11 | * as an argument 12 | */ 13 | class Search extends Component { 14 | constructor(props) { 15 | super(props); 16 | /** 17 | * Initialized state here with default empty string 18 | */ 19 | this.state = { 20 | stockTicker: '' 21 | } 22 | 23 | /** 24 | * Functions are already bound here for you. 25 | */ 26 | this.handleInputChange = this.handleInputChange.bind(this); 27 | this.handleSearch = this.handleSearch.bind(this); 28 | } 29 | 30 | handleInputChange(e) { 31 | /** 32 | * ✏️ 33 | * You need to get the value typed on the input and use that to update 34 | * the `stockTicker` state 35 | *🧭 You can get the value of the input using the SyntheticEvent passed 36 | * to this function as argument. e.target.value here will give you that value 37 | *🧭 Set this value to the state `stockTicker` 38 | */ 39 | } 40 | 41 | handleSearch() { 42 | /** 43 | * ✏️ 44 | * You need to invoke the `handleSearch` props passed by the parent 45 | * Pass the latest `stateTicker` state when you invoke that function 46 | * this.props.onSearch(this.state.stockTicker) 47 | */ 48 | } 49 | 50 | render() { 51 | const { stockTicker } = this.state; 52 | return ( 53 |
54 | 55 | 56 |
57 | ); 58 | } 59 | } 60 | 61 | export default Search; 62 | -------------------------------------------------------------------------------- /src/capstone/solution/Capstone.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import isEmpty from 'lodash/isEmpty'; 3 | 4 | import Search from './Search'; 5 | import CompanyProfile from './CompanyProfile'; 6 | import CompanyFinancial from './CompanyFinancial'; 7 | import style from './style'; 8 | 9 | const EMPTY_TICKER_MSG = 'Please type a stock ticker and click Search button.'; 10 | 11 | class Capstone extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | stockTicker: '' 16 | } 17 | 18 | this.handleSearch = this.handleSearch.bind(this); 19 | } 20 | 21 | handleSearch(stockTicker) { 22 | this.setState({ stockTicker }); 23 | } 24 | 25 | render() { 26 | const EmptyTickerMessage = ( 27 | isEmpty(this.state.stockTicker) && 28 |
{EMPTY_TICKER_MSG}
29 | ) 30 | return ( 31 |
32 | 33 | 34 | 35 | {EmptyTickerMessage} 36 |
37 | ); 38 | } 39 | } 40 | 41 | export default Capstone; -------------------------------------------------------------------------------- /src/capstone/solution/CompanyFinancial.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import isEmpty from 'lodash/isEmpty'; 3 | 4 | import DataAPI from '../../api/DataApi'; 5 | import style from './style'; 6 | 7 | const ERROR_MSG = `Error when fetching company financial data. Please check if you entered valid stock ticker`; 8 | 9 | class CompanyFinancial extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | companyFinancial: {}, 14 | errorMsg: '' 15 | } 16 | 17 | this.updateCompanyFinancial = this.updateCompanyFinancial.bind(this); 18 | this.handleError = this.handleError.bind(this); 19 | } 20 | 21 | componentDidMount() { 22 | this.fetchCompanyFinancial(); 23 | } 24 | 25 | componentDidUpdate(prevProps) { 26 | if (this.props.stockTicker !== prevProps.stockTicker) { 27 | this.fetchCompanyFinancial(); 28 | } 29 | } 30 | 31 | fetchCompanyFinancial() { 32 | const { stockTicker } = this.props; 33 | this.updateErrorMsg(''); 34 | if (stockTicker) { 35 | DataAPI.getCompanyFinancial(this.props.stockTicker) 36 | .then(this.updateCompanyFinancial) 37 | .catch(this.handleError) 38 | } 39 | } 40 | 41 | updateCompanyFinancial(companyFinancial) { 42 | this.setState({ companyFinancial }) 43 | } 44 | 45 | updateErrorMsg(errorMsg) { 46 | this.setState({ errorMsg }); 47 | } 48 | 49 | handleError(error) { 50 | this.updateErrorMsg(ERROR_MSG); 51 | this.updateCompanyFinancial({}); 52 | } 53 | 54 | render() { 55 | const { companyFinancial } = this.state; 56 | const ErrorMsg = ( 57 | !isEmpty(this.state.errorMsg) && 58 |
{this.state.errorMsg}
59 | ) 60 | const CompanyFinancialSection = ( 61 | !isEmpty(this.state.companyFinancial) && 62 |
63 |
Financial
64 |
65 | { 66 | Object.keys(companyFinancial) 67 | .map(prop => { 68 | return
69 |
70 | {prop} 71 |
72 |
73 | {companyFinancial[prop]} 74 |
75 |
76 | }) 77 | } 78 |
79 | 80 |
81 | ) 82 | return ( 83 |
84 | {ErrorMsg} 85 | {CompanyFinancialSection} 86 |
87 | ); 88 | } 89 | } 90 | 91 | export default CompanyFinancial; -------------------------------------------------------------------------------- /src/capstone/solution/CompanyProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import isEmpty from 'lodash/isEmpty'; 3 | import DataAPI from '../../api/DataApi'; 4 | import style from './style'; 5 | 6 | const ERROR_MSG = `Error when fetching company profile data. Please check if you entered valid stock ticker`; 7 | class CompanyProfile extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | companyProfile: {}, 12 | errorMsg: '' 13 | } 14 | this.updateCompanyProfile = this.updateCompanyProfile.bind(this); 15 | this.updateErrorMsg = this.updateErrorMsg.bind(this); 16 | this.handleError = this.handleError.bind(this); 17 | } 18 | 19 | componentDidMount() { 20 | this.fetchCompanyProfile(); 21 | } 22 | 23 | componentDidUpdate(prevProps, prevState) { 24 | if (this.props.stockTicker !== prevProps.stockTicker) { 25 | this.fetchCompanyProfile(); 26 | } 27 | } 28 | 29 | fetchCompanyProfile() { 30 | const { stockTicker } = this.props; 31 | this.updateErrorMsg(''); 32 | if (stockTicker) { 33 | DataAPI.getCompanyProfile(this.props.stockTicker) 34 | .then(this.updateCompanyProfile) 35 | .catch(this.handleError) 36 | } 37 | } 38 | 39 | updateCompanyProfile(companyProfile) { 40 | this.setState({ companyProfile }); 41 | } 42 | 43 | updateErrorMsg(errorMsg) { 44 | this.setState({ errorMsg }); 45 | } 46 | 47 | handleError() { 48 | this.updateErrorMsg(ERROR_MSG); 49 | this.updateCompanyProfile({}); 50 | } 51 | 52 | render() { 53 | const { companyProfile } = this.state; 54 | const { description, ...rest } = companyProfile; 55 | 56 | const ErrorMsg = ( 57 | !isEmpty(this.state.errorMsg) && 58 |
{this.state.errorMsg}
59 | ) 60 | const CompanyProfileSection = ( 61 | !isEmpty(this.state.companyProfile) && 62 |
63 |
64 | {description} 65 |
66 |
67 | { 68 | Object.keys(rest) 69 | .map(data => { 70 | return ( 71 |
72 |
{data.toUpperCase()}
73 |
{companyProfile[data]}
74 |
75 | ) 76 | }) 77 | } 78 |
79 |
80 | ) 81 | return ( 82 |
83 | {ErrorMsg} 84 | {CompanyProfileSection} 85 |
86 | ); 87 | } 88 | } 89 | 90 | export default CompanyProfile; -------------------------------------------------------------------------------- /src/capstone/solution/Search.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import style from './style'; 3 | 4 | class Search extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | stockTicker: '' 9 | } 10 | 11 | this.handleInputChange = this.handleInputChange.bind(this); 12 | this.handleSearch = this.handleSearch.bind(this); 13 | } 14 | handleInputChange(e) { 15 | this.setState({ 16 | stockTicker: e.target.value 17 | }) 18 | } 19 | 20 | handleSearch() { 21 | this.props.onSearch(this.state.stockTicker); 22 | } 23 | 24 | render() { 25 | const { stockTicker } = this.state; 26 | return ( 27 |
28 | 29 | 30 |
31 | ); 32 | } 33 | } 34 | 35 | export default Search; -------------------------------------------------------------------------------- /src/capstone/solution/style.js: -------------------------------------------------------------------------------- 1 | export default { 2 | container: { 3 | fontSize: '0.9rem', 4 | margin: 10 5 | }, 6 | searchInput: { 7 | padding: 9 8 | }, 9 | searchContainer: { 10 | padding: `0px 0px 10px 0px` 11 | }, 12 | financialTitle: { 13 | fontWeight: 'bold', 14 | borderBottom: '1px solid gray', 15 | padding: 10, 16 | backgroundColor: `darkgray` 17 | }, 18 | financialContent: { 19 | padding: 10 20 | }, 21 | financialContainer: { 22 | border: `1px solid gray`, 23 | }, 24 | financialMetricRow: { 25 | display: 'flex', 26 | justifyContent: 'flext-start' 27 | }, 28 | financialMetric: { 29 | width: `30%`, 30 | fontWeight: 'bold' 31 | }, 32 | financialMetricValue: { 33 | 34 | }, 35 | profileContainer: { 36 | display: 'flex', 37 | justifyContent: 'flex-start', 38 | flexWrap: 'wrap' 39 | }, 40 | profileDescription: { 41 | border: `1px solid gray`, 42 | padding: 10, 43 | }, 44 | profileAttrTitle: { 45 | fontWeight: 'bold', 46 | background: 'cadetblue', 47 | color: 'white', 48 | padding: `5px 0px 5px 5px` 49 | }, 50 | profileAttrContainer: { 51 | width: '33%', 52 | padding: `10px 10px 10px 0px` 53 | } 54 | } -------------------------------------------------------------------------------- /src/exercise/01-HelloWorld.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal here is just to say Hello World. 6 | * Follow the instruction inside return statement 7 | */ 8 | function HelloWorld(props) { 9 | return ( 10 | /** 11 | * ✏️ 12 | * Instead of returning null you would need to return a React element 13 | * Use the React.createElement function to display a div 14 | * and Hello World text inside the div 15 | */ 16 | null 17 | ); 18 | } 19 | 20 | /** 21 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 22 | * This is how you would use your above component and 23 | * the output of this code is displayed on the browser 24 | */ 25 | const Usage = (props) => { 26 | return 27 | } 28 | 29 | export default Usage; 30 | 31 | -------------------------------------------------------------------------------- /src/exercise/01-HelloWorld_ts.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal here is just to say Hello World. 6 | * Follow the instruction inside return statement 7 | */ 8 | function HelloWorld(props) { 9 | return ( 10 | /** 11 | * ✏️ 12 | * Instead of returning null you would need to return a React element 13 | * Use the React.createElement function to display a div 14 | * and Hello World text inside the div 15 | */ 16 | null 17 | ); 18 | } 19 | 20 | /** 21 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 22 | * This is how you would use your above component and 23 | * the output of this code is displayed on the browser 24 | */ 25 | const Usage = (props) => { 26 | return 27 | } 28 | 29 | export default Usage; 30 | -------------------------------------------------------------------------------- /src/exercise/02-IntroToJSX.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal here is just to say Hello World. 6 | * It is similar to previous exercise we did except instead of using 7 | * React.createElement function we want to use JSX 8 | */ 9 | function HelloWorld(props){ 10 | return ( 11 | /** 12 | * ✏️ 13 | * Instead of returning null you would need to return a React element 14 | * Unlike earlier exercise where you returned React.createElement 15 | * here you should use JSX to return a div with 'Hello World' 16 | */ 17 | null 18 | ); 19 | } 20 | 21 | /** 22 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 23 | * This is how you would use your above component and 24 | * the output of this code is displayed on the browser 25 | */ 26 | const Usage = (props) => { 27 | return 28 | } 29 | 30 | export default Usage; 31 | 32 | -------------------------------------------------------------------------------- /src/exercise/02-IntroToJSX_ts.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal here is just to say Hello World. 6 | * It is similar to previous exercise we did except instead of using 7 | * React.createElement function we want to use JSX 8 | */ 9 | function HelloWorld(props){ 10 | return ( 11 | /** 12 | * ✏️ 13 | * Instead of returning null you would need to return a React element 14 | * Unlike earlier exercise where you returned React.createElement 15 | * here you should use JSX to return a div with 'Hello World' 16 | */ 17 | null 18 | ); 19 | } 20 | 21 | /** 22 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 23 | * This is how you would use your above component and 24 | * the output of this code is displayed on the browser 25 | */ 26 | const Usage = (props) => { 27 | return 28 | } 29 | 30 | export default Usage; 31 | 32 | -------------------------------------------------------------------------------- /src/exercise/03-PowerOfJSX.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal here is to get you more familiar with JSX. 6 | * You will use javascript code inside JSX to loop through object keys 7 | * and render a div element for each element in that object 8 | */ 9 | function CompanyProfile(props) { 10 | /** 11 | * 💡 some variables to store mock data 12 | * We will use these data to display the company profile information 13 | */ 14 | const stockTicker = 'AAPL'; 15 | const companyProfileInfo = { 16 | 'Company Name': 'Apple Inc.', 17 | 'Price': 150, 18 | 'Exchange': "Nasdaq Global Select", 19 | 'Industry': "Computer Hardware", 20 | 'CEO': 'Timothy D. Cook' 21 | } 22 | 23 | return ( 24 |
25 |
Profile of: {/**✏️ display stock ticker here*/}
26 |
27 |
28 | { 29 | /** 30 | * ✏️ 31 | * This block is surrounded by curly braces {} so 32 | * we can really execute any Javascript stuff here. 33 | * 34 | * Loop through the keys of companyProfileInfo 35 | * object to render one div per key/value pair. The div should 36 | * render key followed by a colon followed by value. 37 | * 38 | * 🧭 Object.keys(obj) can be used to loop through the object 39 | * eg: 40 | * const obj = { 'key1': 'value1', 'key2': 'value2'}; 41 | * Object.keys(obj) will return ['key1', 'key2'] 42 | * 🧭 You can use Array.map() to map any key to a div element 43 | * eg: 44 | * ['a', 'b', 'c'].map(d =>
{d}
) 45 | * 🧭 Remember to use curly braces inside the div to render 46 | * any text content you want 47 | */ 48 | } 49 |
50 |
51 | ); 52 | } 53 | 54 | /** 55 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 56 | * This is how you would use your above component and 57 | * the output of this code is displayed on the browser 58 | */ 59 | const Usage = (props) => { 60 | return 61 | } 62 | 63 | export default Usage; 64 | -------------------------------------------------------------------------------- /src/exercise/03-PowerOfJSX_ts.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal here is to get you more familiar with JSX. 6 | * You will use javascript code inside JSX to loop through object keys 7 | * and render a div element for each element in that object 8 | */ 9 | function CompanyProfile(props) { 10 | /** 11 | * 💡 some variables to store mock data 12 | * We will use these data to display the company profile information 13 | */ 14 | const stockTicker = 'APPL'; 15 | const companyProfileInfo = { 16 | 'Company Name': 'Apple Inc.', 17 | 'Price': 150, 18 | 'Exchange': "Nasdaq Global Select", 19 | 'Industry': "Computer Hardware", 20 | 'CEO': 'Timothy D. Cook' 21 | } 22 | 23 | return ( 24 |
25 |
Profile of: {/**✏️ display stock ticker here*/}
26 |
27 |
28 | { 29 | /** 30 | * ✏️ 31 | * This block is surrounded by curly braces {} so 32 | * we can really execute any Javascript stuff here. 33 | * 34 | * Loop through the keys of companyProfileInfo 35 | * object to render one div per key/value pair. The div should 36 | * render key followed by a colon followed by value. 37 | * 38 | * 🧭 Object.keys(obj) can be used to loop through the object 39 | * eg: 40 | * const obj = { 'key1': 'value1', 'key2': 'value2'}; 41 | * Object.keys(obj) will return ['key1', 'key2'] 42 | * 🧭 You can use Array.map() to map any key to a div element 43 | * eg: 44 | * ['a', 'b', 'c'].map(d =>
{d}
) 45 | * 🧭 Remember to use curly braces inside the div to render 46 | * any text content you want 47 | */ 48 | } 49 |
50 |
51 | ); 52 | } 53 | 54 | /** 55 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 56 | * This is how you would use your above component and 57 | * the output of this code is displayed on the browser 58 | */ 59 | const Usage = (props) => { 60 | return 61 | } 62 | 63 | export default Usage; 64 | -------------------------------------------------------------------------------- /src/exercise/04-Props.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * 🏆 5 | * We are trying to make `CompanyProfile` component reusable 6 | * Unlike previous exercise where we had `stockTicker` and `companyProfileInfo` 7 | * hard-coded inside this component itself, we are now getting it as a props 8 | */ 9 | function CompanyProfile(props) { 10 | /** 11 | * Where do we get stockTicker and companyProfileInfo from? 12 | * Well we get it as "props". The user of this component will 13 | * pass the value for these two variables. 14 | */ 15 | const stockTicker = ''; //✏️ Instead of empty string we want to get this value from props 16 | const companyProfileInfo = {}; //✏️ Instead of empty object we want to get this value from props 17 | return ( 18 |
19 |
Profile of: {stockTicker}
20 |
21 |
22 | { 23 | Object.keys(companyProfileInfo) 24 | .map((info, index) => { 25 | return
{info} : {companyProfileInfo[info]}
26 | }) 27 | } 28 |
29 |
30 | ); 31 | } 32 | 33 | function FBCompanyProfile() { 34 | /** 35 | * We need to pass these data to the `CompanyProfile` component 36 | * as the props 37 | */ 38 | const stockTicker = 'FB'; 39 | const companyProfileInfo = { 40 | 'Company Name': 'Facebook', 41 | 'Price': 150, 42 | 'Exchange': "Nasdaq Global Select", 43 | 'Industry': "Computer Software", 44 | 'CEO': 'Mark Zuckerberg' 45 | } 46 | /** 47 | * ✏️ need to pass the props to the `CompanyProfile` component 48 | * we need to pass `stockTicker` and `companyProfileInfo` 49 | * */ 50 | return ( 51 | 52 | ) 53 | } 54 | 55 | /** 56 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 57 | * This is how you would use your above component and 58 | * the output of this code is displayed on the browser 59 | */ 60 | const Usage = (props) => { 61 | return 62 | } 63 | 64 | export default Usage; 65 | -------------------------------------------------------------------------------- /src/exercise/04-Props_ts.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * 🏆 5 | * We are trying to make `CompanyProfile` component reusable 6 | * Unlike previous exercise where we had `stockTicker` and `companyProfileInfo` 7 | * hard-coded inside this component itself, we are now getting it as a props 8 | */ 9 | function CompanyProfile(props) { 10 | /** 11 | * Where do we get stockTicker and companyProfileInfo from? 12 | * Well we get it as "props". The user of this component will 13 | * pass the value for these two variables. 14 | */ 15 | const stockTicker = ''; //✏️ Instead of empty string we want to get this value from props 16 | const companyProfileInfo = {}; //✏️ Instead of empty object we want to get this value from props 17 | return ( 18 |
19 |
Profile of: {stockTicker}
20 |
21 |
22 | { 23 | Object.keys(companyProfileInfo) 24 | .map((info, index) => { 25 | return
{info} : {companyProfileInfo[info]}
26 | }) 27 | } 28 |
29 |
30 | ); 31 | } 32 | 33 | function FBCompanyProfile() { 34 | /** 35 | * We need to pass these data to the `CompanyProfile` component 36 | * as the props 37 | */ 38 | const stockTicker = 'FB'; 39 | const companyProfileInfo = { 40 | 'Company Name': 'Facebook', 41 | 'Price': 150, 42 | 'Exchange': "Nasdaq Global Select", 43 | 'Industry': "Computer Software", 44 | 'CEO': 'Mark Zuckerberg' 45 | } 46 | /** 47 | * ✏️ need to pass the props to the `CompanyProfile` component 48 | * we need to pass `stockTicker` and `companyProfileInfo` 49 | * */ 50 | return ( 51 | 52 | ) 53 | } 54 | 55 | /** 56 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 57 | * This is how you would use your above component and 58 | * the output of this code is displayed on the browser 59 | */ 60 | const Usage = (props) => { 61 | return 62 | } 63 | 64 | export default Usage; 65 | -------------------------------------------------------------------------------- /src/exercise/05-State.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | * 🏆 5 | * Here we have a Counter component that display the current value of the counter 6 | * It also has two buttons to increase ('+') or decrease('-') the counter. 7 | * The counter value will be stored in the state. 8 | * You need to update the state to add 1 to the counter when 9 | * "+" is clicked and substract 1 to the current when "-" is clicked 10 | */ 11 | class Counter extends Component { 12 | constructor(props){ 13 | super(props); 14 | /** 15 | * ✏️ 16 | * Initialize a state here with initial value of counter set to 0 17 | * this.state = { counter: defaultValue } 18 | */ 19 | this.state = {}; 20 | 21 | /** 22 | * 💡 23 | * We are binding the methods here, don't worry about this right now 24 | * We will look at why we do this later in the tutorial 25 | */ 26 | this.increment = this.increment.bind(this); 27 | this.decrement = this.decrement.bind(this); 28 | } 29 | 30 | /** 31 | *💡 32 | * This method will be called when the user clicks "+" button to increase the counter 33 | */ 34 | increment(){ 35 | /** 36 | * ✏️ 37 | * You need to call setState here to update the `counter` state 38 | * When user clicks the "+" we need to add 1 to the current state and 39 | * set the state with the new value. 40 | * We need to use value of current state to derive the new state, 41 | * so it's better to use the updater function like 42 | * this.setState(function(currentState) { 43 | * return newState 44 | * }); 45 | */ 46 | } 47 | 48 | /** 49 | *💡 50 | * This method will be called when the user clicks "-" button to decrease the counter 51 | */ 52 | decrement(){ 53 | /** 54 | * ✏️ 55 | * You need to call setState here to update the `counter` state 56 | * When user clicks the "-" we need to subtract 1 to the current state and 57 | * set the state with the new value. 58 | * We need to use value of current state to derive the new state, 59 | * so it's better for us to use the updater function like 60 | * this.setState(function(currentState) { 61 | * return newState 62 | * }); 63 | */ 64 | } 65 | 66 | render() { 67 | return ( 68 |
69 |
71 | - 72 |
73 |
74 | {this.state.counter} 75 |
76 |
78 | + 79 |
80 |
81 | ); 82 | } 83 | } 84 | 85 | /** 86 | * 💡 87 | * This is just some styling used 88 | * You don't need to worry about this or change this 89 | */ 90 | const style = { 91 | container: { 92 | display: 'flex' 93 | }, 94 | buttons: { 95 | padding: `0px 7px 0px 7px`, 96 | backgroundColor: 'grey', 97 | cursor: 'pointer' 98 | }, 99 | counter: { 100 | padding: `0px 7px 0px 7px` 101 | } 102 | } 103 | 104 | 105 | /** 106 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 107 | * This is how you would use your above component and 108 | * the output of this code is displayed on the browser 109 | */ 110 | const Usage = (props) => { 111 | return 112 | }; 113 | 114 | export default Usage; -------------------------------------------------------------------------------- /src/exercise/05-State_ts.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | * 🏆 5 | * Here we have a Counter component that display the current value of the counter 6 | * It also has two buttons to increase ('+') or decrease('-') the counter. 7 | * The counter value will be stored in the state. 8 | * You need to update the state to add 1 to the counter when 9 | * "+" is clicked and substract 1 to the current when "-" is clicked 10 | */ 11 | class Counter extends Component { 12 | constructor(props){ 13 | super(props); 14 | /** 15 | * ✏️ 16 | * Initialize a state here with initial value of counter set to 0 17 | * this.state = { counter: defaultValue } 18 | */ 19 | this.state = {}; 20 | 21 | /** 22 | * 💡 23 | * We are binding the methods here, don't worry about this right now 24 | * We will look at why we do this later in the tutorial 25 | */ 26 | this.increment = this.increment.bind(this); 27 | this.decrement = this.decrement.bind(this); 28 | } 29 | 30 | /** 31 | *💡 32 | * This method will be called when the user clicks "+" button to increase the counter 33 | */ 34 | increment(){ 35 | /** 36 | * ✏️ 37 | * You need to call setState here to update the `counter` state 38 | * When user clicks the "+" we need to add 1 to the current state and 39 | * set the state with the new value. 40 | * We need to use value of current state to derive the new state, 41 | * so it's better to use the updater function like 42 | * this.setState(function(currentState) { 43 | * return newState 44 | * }); 45 | */ 46 | } 47 | 48 | /** 49 | *💡 50 | * This method will be called when the user clicks "-" button to decrease the counter 51 | */ 52 | decrement(){ 53 | /** 54 | * ✏️ 55 | * You need to call setState here to update the `counter` state 56 | * When user clicks the "-" we need to subtract 1 to the current state and 57 | * set the state with the new value. 58 | * We need to use value of current state to derive the new state, 59 | * so it's better for us to use the updater function like 60 | * this.setState(function(currentState) { 61 | * return newState 62 | * }); 63 | */ 64 | } 65 | 66 | render() { 67 | return ( 68 |
69 |
71 | - 72 |
73 |
74 | {this.state.counter} 75 |
76 |
78 | + 79 |
80 |
81 | ); 82 | } 83 | } 84 | 85 | /** 86 | * 💡 87 | * This is just some styling used 88 | * You don't need to worry about this or change this 89 | */ 90 | const style = { 91 | container: { 92 | display: 'flex' 93 | }, 94 | buttons: { 95 | padding: `0px 7px 0px 7px`, 96 | backgroundColor: 'grey', 97 | cursor: 'pointer' 98 | }, 99 | counter: { 100 | padding: `0px 7px 0px 7px` 101 | } 102 | } 103 | 104 | 105 | /** 106 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 107 | * This is how you would use your above component and 108 | * the output of this code is displayed on the browser 109 | */ 110 | const Usage = (props) => { 111 | return 112 | }; 113 | 114 | export default Usage; 115 | -------------------------------------------------------------------------------- /src/exercise/06-LifecycleMethods.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import DataAPI from '../api/DataApi'; 3 | 4 | /** 5 | *🏆 6 | * This component is similar to what we looked at for the exercise of `Understanding Props` 7 | * section (./04-Props.js) but there are some differences. 8 | * We expect the user of this component to pass `stockTicker` as props. 9 | * But one difference here compared to earlier exercise is that we are not expecting `companyProfileInfo` 10 | * to be passed as props. Instead we have an API that we would like to use to 11 | * fetch the data from. It has a method called `getCompanyProfile(ticker)` 12 | * that will return you `companyProfile` for given ticker. 13 | * The goal is to help you understand some use cases where we might want to use 14 | * some of the React lifecycle methods 15 | */ 16 | class CompanyProfile extends Component { 17 | constructor(props) { 18 | super(props); 19 | /** 20 | * We have initialized the state with companyProfileInformation 21 | */ 22 | this.state = { 23 | companyProfileInfo: {} 24 | } 25 | } 26 | 27 | /** 28 | * ✏️ 29 | * We need to use componentDidMount lifecycle method to fetch company profile 30 | * information for given stock ticker using the DataAPI provided 31 | * 🧭 Add lifecycle method called componentDidMount 32 | * 🧭 Inside that method you need to use the DataAPI that's already imported. 33 | * Make a call to `getCompanyProfile()` method and pass the `stockTicker` from the props. 34 | * This method will return a promise that resolves into `companyProfile` info 35 | * 🧭 Using the data from the promise use `setState` to set companyProfileInfo 36 | * like - `this.setState({ companyProfileInfo: data })` 37 | * 🧭 What if the promise resolves into an error? You might want to catch the error 38 | * and do something with it (Remember .catch in Promise). For example below I'm 39 | * catching an error and just logging it in console. You can do the same for the 40 | * sake of this exercise: 41 | * Api.getData() 42 | * .then(data => doSth(data)) 43 | * .catch(error => console.log(error)) 44 | * */ 45 | componentDidMount() { 46 | 47 | } 48 | 49 | render() { 50 | const stockTicker = this.props.stockTicker; 51 | const companyProfileInfo = this.state.companyProfileInfo; 52 | return ( 53 |
54 |
Profile of: {stockTicker}
55 |
56 |
57 | { 58 | Object.keys(companyProfileInfo) 59 | .map((info, index) => { 60 | return
{info.toUpperCase()} : {companyProfileInfo[info]}
61 | }) 62 | } 63 |
64 |
65 | ); 66 | } 67 | } 68 | 69 | /** 70 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 71 | * This is how you would use your above component and 72 | * the output of this code is displayed on the browser 73 | */ 74 | const Usage = (props) => { 75 | return 76 | } 77 | 78 | export default Usage; -------------------------------------------------------------------------------- /src/exercise/06-LifecycleMethods_ts.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import DataAPI from '../api/DataApi'; 3 | 4 | /** 5 | *🏆 6 | * This component is similar to what we looked at for the exercise of `Understanding Props` 7 | * section (./04-Props.js) but there are some differences. 8 | * We expect the user of this component to pass `stockTicker` as props. 9 | * But one difference here compared to earlier exercise is that we are not expecting `companyProfileInfo` 10 | * to be passed as props. Instead we have an API that we would like to use to 11 | * fetch the data from. It has a method called `getCompanyProfile(ticker)` 12 | * that will return you `companyProfile` for given ticker. 13 | * The goal is to help you understand some use cases where we might want to use 14 | * some of the React lifecycle methods 15 | */ 16 | class CompanyProfile extends Component { 17 | constructor(props) { 18 | super(props); 19 | /** 20 | * We have initialized the state with companyProfileInformation 21 | */ 22 | this.state = { 23 | companyProfileInfo: {} 24 | } 25 | } 26 | 27 | /** 28 | * ✏️ 29 | * We need to use componentDidMount lifecycle method to fetch company profile 30 | * information for given stock ticker using the DataAPI provided 31 | * 🧭 Add lifecycle method called componentDidMount 32 | * 🧭 Inside that method you need to use the DataAPI that's already imported. 33 | * Make a call to `getCompanyProfile()` method and pass the `stockTicker` from the props. 34 | * This method will return a promise that resolves into `companyProfile` info 35 | * 🧭 Using the data from the promise use `setState` to set companyProfileInfo 36 | * like - `this.setState({ companyProfileInfo: data })` 37 | * 🧭 What if the promise resolves into an error? You might want to catch the error 38 | * and do something with it (Remember .catch in Promise). For example below I'm 39 | * catching an error and just logging it in console. You can do the same for the 40 | * sake of this exercise: 41 | * Api.getData() 42 | * .then(data => doSth(data)) 43 | * .catch(error => console.log(error)) 44 | * */ 45 | componentDidMount() { 46 | 47 | } 48 | 49 | render() { 50 | const stockTicker = this.props.stockTicker; 51 | const companyProfileInfo = this.state.companyProfileInfo; 52 | return ( 53 |
54 |
Profile of: {stockTicker}
55 |
56 |
57 | { 58 | Object.keys(companyProfileInfo) 59 | .map((info, index) => { 60 | return
{info.toUpperCase()} : {companyProfileInfo[info]}
61 | }) 62 | } 63 |
64 |
65 | ); 66 | } 67 | } 68 | 69 | /** 70 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 71 | * This is how you would use your above component and 72 | * the output of this code is displayed on the browser 73 | */ 74 | const Usage = (props) => { 75 | return 76 | } 77 | 78 | export default Usage; 79 | -------------------------------------------------------------------------------- /src/exercise/07-HandlingEvents.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal of this exercise is to get you more familiar with event handling 6 | * in React. Here we would render an input element and add function to handle 7 | * events on that input element 8 | */ 9 | class FancyInput extends Component { 10 | constructor(props) { 11 | super(props); 12 | /** 13 | * 💡 Here we have initialized the state with inputValue 14 | */ 15 | this.state = { 16 | inputValue: '' 17 | } 18 | 19 | /** 20 | * ✏️ 21 | * Need to bind the handleChange function to appropriate `this` 22 | */ 23 | } 24 | 25 | /** 26 | * ✏️ 27 | * Need to get the value of the input and set it to the state 28 | * 🧭 Get the value of the input from the synthetic event 29 | * You can get the value by using event.target.value. 30 | * 🧭 Set the value to the state `inputValue` by calling `setState` 31 | */ 32 | handleChange(e) { 33 | 34 | } 35 | 36 | render() { 37 | 38 | return ( 39 | 40 | { 41 | /** 42 | * ✏️ 43 | * Need to pass the event handler to the input element. 44 | * In this case we need to pass handleChange function to the 45 | * onChange event 46 | */ 47 | } 48 | 49 | { 50 | /** 51 | * 💡 52 | * This div will mirror the user input. For this to work though 53 | * you need to add the handleChange event on the input above 54 | * and update the state when the change happens on the input 55 | */ 56 | } 57 |
You typed: {this.state.inputValue}
58 |
59 | 60 | ) 61 | } 62 | } 63 | 64 | /** 65 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 66 | * This is how you would use your above component 67 | * The output of this code is displayed on the browser on the left hand side 68 | */ 69 | const Usage = (props) => { 70 | return 71 | } 72 | 73 | export default Usage; -------------------------------------------------------------------------------- /src/exercise/07-HandlingEvents_ts.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal of this exercise is to get you more familiar with event handling 6 | * in React. Here we would render an input element and add function to handle 7 | * events on that input element 8 | */ 9 | class FancyInput extends Component { 10 | constructor(props) { 11 | super(props); 12 | /** 13 | * 💡 Here we have initialized the state with inputValue 14 | */ 15 | this.state = { 16 | inputValue: '' 17 | } 18 | 19 | /** 20 | * ✏️ 21 | * Need to bind the handleChange function to appropriate `this` 22 | */ 23 | } 24 | 25 | /** 26 | * ✏️ 27 | * Need to get the value of the input and set it to the state 28 | * 🧭 Get the value of the input from the synthetic event 29 | * You can get the value by using event.target.value. 30 | * 🧭 Set the value to the state `inputValue` by calling `setState` 31 | */ 32 | handleChange(e) { 33 | 34 | } 35 | 36 | render() { 37 | 38 | return ( 39 | 40 | { 41 | /** 42 | * ✏️ 43 | * Need to pass the event handler to the input element. 44 | * In this case we need to pass handleChange function to the 45 | * onChange event 46 | */ 47 | } 48 | 49 | { 50 | /** 51 | * 💡 52 | * This div will mirror the user input. For this to work though 53 | * you need to add the handleChange event on the input above 54 | * and update the state when the change happens on the input 55 | */ 56 | } 57 |
You typed: {this.state.inputValue}
58 |
59 | 60 | ) 61 | } 62 | } 63 | 64 | /** 65 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 66 | * This is how you would use your above component 67 | * The output of this code is displayed on the browser on the left hand side 68 | */ 69 | const Usage = (props) => { 70 | return 71 | } 72 | 73 | export default Usage; 74 | -------------------------------------------------------------------------------- /src/exercise/08-ComposingComponents.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal of this exercise is to get you aquainted with composing 6 | * different components in React. Here we will create a simple Card 7 | * component with Header, Body and Footer section. Nothing fancy - each 8 | * section has different background-color and each section should be 9 | * agnostic to what it's displaying. The user of the card 10 | * should pass "render props" so that the Card can render contents 11 | * to different section without knowing what they are displaying 12 | */ 13 | class Card extends Component { 14 | render() { 15 | return ( 16 |
17 |
18 | { 19 | /** 20 | * ✏️ 21 | * Use renderHeader props here to render header content 22 | */ 23 | } 24 |
25 |
26 | { 27 | /** 28 | * ✏️ 29 | * Use renderBody props here to render body content 30 | */ 31 | } 32 |
33 |
34 | { 35 | /** 36 | * ✏️ 37 | * Use renderFooter props here to render footer content 38 | */ 39 | } 40 |
41 |
42 | ) 43 | } 44 | } 45 | 46 | class CardUser extends Component { 47 | render() { 48 | /** 49 | * ✏️ 50 | * We need to pass renderHeader, renderBody and renderFooter props 51 | * to the Card with what we wanted to display inside that component 52 | * 🧭 Render props are functions when executed return something to render 53 | * 🧭 For simplicity with each render props function you can return a div 54 | * with the text saying which section it is. 55 | * For ex renderHeader can return a
Header
: 56 | * renderHeader={() =>
Header
} 57 | */ 58 | return ( 59 | 60 | ) 61 | } 62 | } 63 | 64 | 65 | /** 66 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 67 | * This is how you would use your above component 68 | * The output of this code is displayed on the browser on the left hand side 69 | */ 70 | const Usage = (props) => { 71 | return 72 | } 73 | 74 | export default Usage; 75 | -------------------------------------------------------------------------------- /src/exercise/08-ComposingComponents_ts.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | *🏆 5 | * The goal of this exercise is to get you aquainted with composing 6 | * different components in React. Here we will create a simple Card 7 | * component with Header, Body and Footer section. Nothing fancy - each 8 | * section has different background-color and each section should be 9 | * agnostic to what it's displaying. The user of the card 10 | * should pass "render props" so that the Card can render contents 11 | * to different section without knowing what they are displaying 12 | */ 13 | class Card extends Component { 14 | render() { 15 | return ( 16 |
17 |
18 | { 19 | /** 20 | * ✏️ 21 | * Use renderHeader props here to render header content 22 | */ 23 | } 24 |
25 |
26 | { 27 | /** 28 | * ✏️ 29 | * Use renderBody props here to render body content 30 | */ 31 | } 32 |
33 |
34 | { 35 | /** 36 | * ✏️ 37 | * Use renderFooter props here to render footer content 38 | */ 39 | } 40 |
41 |
42 | ) 43 | } 44 | } 45 | 46 | class CardUser extends Component { 47 | render() { 48 | /** 49 | * ✏️ 50 | * We need to pass renderHeader, renderBody and renderFooter props 51 | * to the Card with what we wanted to display inside that component 52 | * 🧭 Render props are functions when executed return something to render 53 | * 🧭 For simplicity with each render props function you can return a div 54 | * with the text saying which section it is. 55 | * For ex renderHeader can return a
Header
: 56 | * renderHeader={() =>
Header
} 57 | */ 58 | return ( 59 | 60 | ) 61 | } 62 | } 63 | 64 | 65 | /** 66 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 67 | * This is how you would use your above component 68 | * The output of this code is displayed on the browser on the left hand side 69 | */ 70 | const Usage = (props) => { 71 | return 72 | } 73 | 74 | export default Usage; 75 | -------------------------------------------------------------------------------- /src/exercise/solution/01-HelloWorld-solution.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function HelloWorld(props){ 4 | return ( 5 | React.createElement('div', null, 'Hello World') 6 | ); 7 | } 8 | 9 | /** 10 | * !!!!!!!!!!!!!!!!!!!!!!!!!!! 11 | * DO NOT DELETE OR CHANGE THIS. 12 | * This is how you would use your above component and 13 | * the output of this code is displayed on the browser 14 | */ 15 | const Usage = (props) => { 16 | return 17 | } 18 | 19 | export default Usage; 20 | 21 | -------------------------------------------------------------------------------- /src/exercise/solution/02-IntroToJSX-solution.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function HelloWorld(props){ 4 | return ( 5 |
Hello World
6 | ); 7 | } 8 | 9 | /** 10 | * !!!!!!!!!!!!!!!!!!!!!!!!!!! 11 | * DO NOT DELETE OR CHANGE THIS. 12 | * This is how you would use your above component and 13 | * the output of this code is displayed on the browser 14 | */ 15 | const Usage = (props) => { 16 | return 17 | } 18 | 19 | export default Usage; 20 | 21 | -------------------------------------------------------------------------------- /src/exercise/solution/03-PowerOfJSX-solution.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function StockProfile(props) { 4 | const stockTicker = 'AAPL'; 5 | const companyProfileInfo = { 6 | 'Company Name': 'Apple Inc.', 7 | 'Price': 150, 8 | 'Exchange': "Nasdaq Global Select", 9 | 'Industry': "Computer Hardware", 10 | 'CEO': 'Timothy D. Cook' 11 | } 12 | 13 | return ( 14 |
15 |
Profile of: {stockTicker}
16 |
17 |
18 | { 19 | Object.keys(companyProfileInfo) 20 | .map((info, index) => { 21 | return
{info} : {companyProfileInfo[info]}
22 | }) 23 | } 24 |
25 |
26 | ); 27 | } 28 | 29 | /** 30 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 31 | * This is how you would use your above component and 32 | * the output of this code is displayed on the browser 33 | */ 34 | const Usage = (props) => { 35 | return 36 | } 37 | 38 | export default Usage; 39 | -------------------------------------------------------------------------------- /src/exercise/solution/04-Props-solution.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function StockProfile(props) { 4 | 5 | const stockTicker = props.stockTicker; 6 | const companyProfileInfo = props.companyProfileInfo; 7 | return ( 8 |
9 |
Profile of: {stockTicker}
10 |
11 |
12 | { 13 | Object.keys(companyProfileInfo) 14 | .map((info, index) => { 15 | return
{info} : {companyProfileInfo[info]}
16 | }) 17 | } 18 |
19 |
20 | ); 21 | } 22 | 23 | function FBStockProfile() { 24 | const stockTicker = 'FB'; 25 | const companyProfileInfo = { 26 | 'Company Name': 'Facebook', 27 | 'Price': 150, 28 | 'Exchange': "Nasdaq Global Select", 29 | 'Industry': "Computer Software", 30 | 'CEO': 'Mark Zuckerberg' 31 | } 32 | 33 | return ( 34 | 35 | ) 36 | } 37 | 38 | /** 39 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 40 | * This is how you would use your above component and 41 | * the output of this code is displayed on the browser 42 | */ 43 | const Usage = (props) => { 44 | return 45 | } 46 | 47 | export default Usage; 48 | -------------------------------------------------------------------------------- /src/exercise/solution/05-State-solution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Counter extends Component { 4 | constructor(props){ 5 | super(props); 6 | this.state = { 7 | counter: 0 8 | } 9 | this.increment = this.increment.bind(this); 10 | this.decrement = this.decrement.bind(this); 11 | } 12 | 13 | increment(){ 14 | this.setState(oldState => { 15 | return { counter: oldState.counter + 1 } 16 | }); 17 | } 18 | 19 | decrement(){ 20 | this.setState(oldState => { 21 | return { counter: oldState.counter - 1 } 22 | }); 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 |
30 | - 31 |
32 |
33 | {this.state.counter} 34 |
35 |
37 | + 38 |
39 |
40 | ); 41 | } 42 | } 43 | 44 | const style = { 45 | container: { 46 | display: 'flex' 47 | }, 48 | buttons: { 49 | padding: `0px 7px 0px 7px`, 50 | backgroundColor: 'grey', 51 | cursor: 'pointer' 52 | }, 53 | counter: { 54 | padding: `0px 7px 0px 7px` 55 | } 56 | } 57 | 58 | 59 | /** 60 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 61 | * This is how you would use your above component and 62 | * the output of this code is displayed on the browser 63 | */ 64 | const Usage = (props) => { 65 | return 66 | }; 67 | 68 | export default Usage; -------------------------------------------------------------------------------- /src/exercise/solution/06-LifecycleMethods-solution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import DataAPI from '../../api/DataApi'; 3 | 4 | class CompanyProfile extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | companyProfileInfo: {} 9 | } 10 | } 11 | 12 | componentDidMount() { 13 | DataAPI.getCompanyProfile(this.props.stockTicker) 14 | .then(data => { 15 | this.setState({ 16 | companyProfileInfo: data 17 | }) 18 | }) 19 | .catch(e => console.log(e)); 20 | } 21 | 22 | render() { 23 | const stockTicker = this.props.stockTicker; 24 | const companyProfileInfo = this.state.companyProfileInfo; 25 | return ( 26 |
27 |
Profile of: {stockTicker}
28 |
29 |
30 | { 31 | Object.keys(companyProfileInfo) 32 | .map((info, index) => { 33 | return
{info.toUpperCase()} : {companyProfileInfo[info]}
34 | }) 35 | } 36 |
37 |
38 | ); 39 | } 40 | } 41 | 42 | /** 43 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 44 | * This is how you would use your above component and 45 | * the output of this code is displayed on the browser 46 | */ 47 | const Usage = (props) => { 48 | return 49 | } 50 | 51 | export default Usage; -------------------------------------------------------------------------------- /src/exercise/solution/07-HandlingEvents-solution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class FancyInput extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | inputValue: '' 8 | } 9 | this.handleChange = this.handleChange.bind(this); 10 | } 11 | 12 | handleChange(e) { 13 | this.setState({ 14 | inputValue: e.target.value 15 | }); 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 | 22 |
You typed: {this.state.inputValue}
23 |
24 | 25 | ) 26 | } 27 | } 28 | 29 | /** 30 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 31 | * This is how you would use your above component 32 | * The output of this code is displayed on the browser on the left hand side 33 | */ 34 | const Usage = (props) => { 35 | return 36 | } 37 | 38 | export default Usage; -------------------------------------------------------------------------------- /src/exercise/solution/08-ComposingComponents-solution.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Card extends Component { 4 | render() { 5 | return ( 6 |
7 |
8 | { 9 | this.props.renderHeader() 10 | } 11 |
12 |
13 | { 14 | this.props.renderBody() 15 | } 16 |
17 |
18 | { 19 | this.props.renderFooter() 20 | } 21 |
22 |
23 | ) 24 | } 25 | } 26 | 27 | class CardUser extends Component { 28 | render() { 29 | return ( 30 |
Header
} 31 | renderFooter={() =>
Footer
} 32 | renderBody={() =>
Body
} 33 | /> 34 | ) 35 | } 36 | } 37 | 38 | 39 | /** 40 | * 🚨 🚨 DO NOT DELETE OR CHANGE THIS.🚨 🚨 41 | * This is how you would use your above component 42 | * The output of this code is displayed on the browser on the left hand side 43 | */ 44 | const Usage = (props) => { 45 | return 46 | } 47 | 48 | export default Usage; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | border-radius: 5px; 15 | background-color: #ccc7c2; 16 | padding: 0px 5px 0px 5px; 17 | } 18 | 19 | blockquote { 20 | border-left: solid 5px #9ba1b1; 21 | padding-left: 20px; 22 | font-style: italic; 23 | } 24 | 25 | .App a { 26 | color: #3f51b5; 27 | text-decoration: none; 28 | } 29 | 30 | button { 31 | padding: 10px; 32 | color: white; 33 | /* background-color: #49747b; */ 34 | background-color: #3f51b5; 35 | cursor: pointer; 36 | } 37 | 38 | .App { 39 | background-color: #f1f1f1; 40 | min-height: 100vh; 41 | /* display: flex; */ 42 | /* flex-direction: column; */ 43 | font-size: 1rem; 44 | font-weight: 400; 45 | line-height: 24px; 46 | color: black; 47 | } 48 | 49 | .cardHeader { 50 | background-color: grey; 51 | 52 | } 53 | 54 | .cardBody { 55 | background-color: green; 56 | } 57 | 58 | .cardFooter { 59 | background-color: yellow 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | if (module.hot) { 9 | module.hot.accept('./App', () => { 10 | ReactDOM.render(, document.getElementById('root')) 11 | }) 12 | } -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/setup/AppShell.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | import { withStyles } from '@material-ui/core/styles'; 5 | import Drawer from '@material-ui/core/Drawer'; 6 | import CssBaseline from '@material-ui/core/CssBaseline'; 7 | import AppBar from '@material-ui/core/AppBar'; 8 | import Toolbar from '@material-ui/core/Toolbar'; 9 | import List from '@material-ui/core/List'; 10 | import Divider from '@material-ui/core/Divider'; 11 | import IconButton from '@material-ui/core/IconButton'; 12 | import MenuIcon from '@material-ui/icons/Menu'; 13 | import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; 14 | import Home from '@material-ui/icons/Home'; 15 | import ChevronRightIcon from '@material-ui/icons/ChevronRight'; 16 | import ListItem from '@material-ui/core/ListItem'; 17 | import ListItemText from '@material-ui/core/ListItemText'; 18 | import { Link } from 'react-router-dom'; 19 | 20 | import { TutorialMetadataApi } from './TutorialMetadataApi'; 21 | 22 | const drawerWidth = 260; 23 | 24 | const styles = theme => ({ 25 | root: { 26 | display: 'flex', 27 | }, 28 | appBar: { 29 | transition: theme.transitions.create(['margin', 'width'], { 30 | easing: theme.transitions.easing.sharp, 31 | duration: theme.transitions.duration.leavingScreen, 32 | }), 33 | }, 34 | appBarShift: { 35 | width: `calc(100% - ${drawerWidth}px)`, 36 | marginLeft: drawerWidth, 37 | transition: theme.transitions.create(['margin', 'width'], { 38 | easing: theme.transitions.easing.easeOut, 39 | duration: theme.transitions.duration.enteringScreen, 40 | }), 41 | }, 42 | menuButton: { 43 | marginLeft: 12, 44 | marginRight: 20, 45 | }, 46 | hide: { 47 | display: 'none', 48 | }, 49 | leftMenuNav: { 50 | color: '#0052CC', 51 | textDecoration: 'none', 52 | fontWeight: '400' 53 | }, 54 | drawer: { 55 | width: drawerWidth, 56 | flexShrink: 0, 57 | }, 58 | drawerPaper: { 59 | width: drawerWidth, 60 | }, 61 | drawerHeader: { 62 | display: 'flex', 63 | alignItems: 'center', 64 | padding: '0 8px', 65 | ...theme.mixins.toolbar, 66 | justifyContent: 'flex-end', 67 | }, 68 | content: { 69 | flexGrow: 1, 70 | padding: theme.spacing.unit * 3, 71 | transition: theme.transitions.create('margin', { 72 | easing: theme.transitions.easing.sharp, 73 | duration: theme.transitions.duration.leavingScreen, 74 | }), 75 | marginLeft: -drawerWidth, 76 | }, 77 | contentShift: { 78 | transition: theme.transitions.create('margin', { 79 | easing: theme.transitions.easing.easeOut, 80 | duration: theme.transitions.duration.enteringScreen, 81 | }), 82 | marginLeft: 0, 83 | }, 84 | }); 85 | 86 | class PersistentDrawerLeft extends React.Component { 87 | state = { 88 | open: false, 89 | }; 90 | 91 | handleDrawerOpen = () => { 92 | this.setState({ open: true }); 93 | }; 94 | 95 | handleDrawerClose = () => { 96 | this.setState({ open: false }); 97 | }; 98 | 99 | render() { 100 | const { classes, theme } = this.props; 101 | const { open } = this.state; 102 | 103 | return ( 104 |
105 | 106 | 112 | 113 | 119 | 120 | 121 |
122 | {this.props.title} 123 |
124 |
125 | 126 |
127 |
128 |
129 | 138 |
139 | 140 | {theme.direction === 'ltr' ? : } 141 | 142 |
143 | 144 | 145 | {TutorialMetadataApi.getTutorialMetadata().map((tutorial, index) => ( 146 | 147 | 148 | {tutorial.displayName} 149 | 150 | 151 | ))} 152 | 153 |
154 |
155 | ); 156 | } 157 | } 158 | 159 | PersistentDrawerLeft.propTypes = { 160 | classes: PropTypes.object.isRequired, 161 | theme: PropTypes.object.isRequired, 162 | }; 163 | 164 | export default withStyles(styles, { withTheme: true })(PersistentDrawerLeft); 165 | -------------------------------------------------------------------------------- /src/setup/Exercise.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import Solution from './Solution'; 4 | 5 | const Container = styled.div` 6 | display: flex; 7 | `; 8 | 9 | const SolutionContainer = styled.div` 10 | margin: 5px; 11 | width: 50%; 12 | min-height: 100px; 13 | `; 14 | 15 | const SolutionTitle = styled.div` 16 | padding-bottom: 10px; 17 | font-size: 0.9rem; 18 | `; 19 | 20 | const Exercise = props => { 21 | const { 22 | exercise: { location, solutionLocation } 23 | } = props; 24 | return ( 25 | 26 | 27 | Your Solution: ({location}) 28 | 29 | 30 |
31 | 32 | Target Solution: ({solutionLocation}) 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default Exercise; 40 | -------------------------------------------------------------------------------- /src/setup/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { TutorialMetadataApi } from './TutorialMetadataApi'; 3 | import { Link } from 'react-router-dom'; 4 | import styled from 'styled-components'; 5 | 6 | const Container = styled.div` 7 | padding: 10px; 8 | margin: 10px; 9 | text-align: center; 10 | ` 11 | 12 | const Title = styled.div` 13 | font-weight: bold; 14 | font-size: 2rem; 15 | padding: 15px; 16 | ` 17 | 18 | const TutorialNavigation = styled.div` 19 | margin-top: 20px; 20 | margin-left: calc(50% - 100px); 21 | text-align: left; 22 | `; 23 | 24 | const TutorialLink = styled.div` 25 | padding: 10px; 26 | font-size: 1.3rem; 27 | font-weight: bold; 28 | `; 29 | 30 | class Home extends Component { 31 | constructor(props) { 32 | super(props); 33 | this.state = { 34 | tutorialMetadata: [] 35 | } 36 | } 37 | 38 | componentDidMount(){ 39 | this.setState({ 40 | tutorialMetadata: TutorialMetadataApi.getTutorialMetadata() 41 | }) 42 | } 43 | render() { 44 | const { tutorialMetadata } = this.state; 45 | return ( 46 | 47 | 48 | Welcome to Learn React App! 49 | 50 |
51 | Jump to any tutorial as you like but it's recommended to go in order. 52 |
53 | 54 | { 55 | tutorialMetadata.map((data, index) => { 56 | return 57 | {data.displayName} 58 | 59 | }) 60 | } 61 | 62 | 63 |
64 | ) 65 | } 66 | } 67 | 68 | export default Home; 69 | -------------------------------------------------------------------------------- /src/setup/Markdown.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ReactMarkdown from 'react-markdown'; 4 | 5 | import Exercise from './Exercise'; 6 | import FileReader from '../FileReader'; 7 | 8 | import codeRenderer from './renderer/CodeBlock'; 9 | import linkRenderer from './renderer/HyperLink'; 10 | 11 | const EXERCISE_SPLITTER = `` 12 | 13 | class Markdown extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | markdownSource: [] 18 | } 19 | } 20 | 21 | componentDidMount() { 22 | this.stitchMarkdownSource(this.props); 23 | } 24 | 25 | componentDidUpdate(prevProps) { 26 | if (prevProps.tutorialMetadata.markdownLocation !== this.props.tutorialMetadata.markdownLocation) { 27 | this.stitchMarkdownSource(this.props); 28 | } 29 | } 30 | 31 | stitchMarkdownSource({ tutorialMetadata }) { 32 | FileReader.readFile(`${tutorialMetadata.markdownLocation}`) 33 | .then(markdownText => { 34 | let stichedSource = []; 35 | const hasExercise = markdownText.includes(EXERCISE_SPLITTER); 36 | if (hasExercise) { 37 | stichedSource = []; 38 | const splitSource = markdownText.split(EXERCISE_SPLITTER); 39 | splitSource.forEach((s, index) => { 40 | stichedSource.push( 41 | 49 | ); 50 | if (index < splitSource.length - 1) { 51 | stichedSource.push( 52 | 56 | ); 57 | } 58 | }); 59 | } else { 60 | stichedSource = [ 61 | ] 69 | } 70 | this.setState({ 71 | markdownSource: stichedSource 72 | }); 73 | }) 74 | } 75 | 76 | render() { 77 | return ( 78 |
79 | {this.state.markdownSource.map(d => d)} 80 |
81 | ); 82 | } 83 | } 84 | 85 | Markdown.propTypes = { 86 | source: PropTypes.string 87 | }; 88 | 89 | export default Markdown; -------------------------------------------------------------------------------- /src/setup/ScrollToTop.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router'; 3 | 4 | class ScrollToTop extends React.Component { 5 | componentDidUpdate(prevProps) { 6 | if (this.props.location !== prevProps.location) { 7 | window.scrollTo(0, 0); 8 | } 9 | } 10 | 11 | render() { 12 | return this.props.children; 13 | } 14 | } 15 | 16 | export default withRouter(ScrollToTop); -------------------------------------------------------------------------------- /src/setup/Solution.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | import FileReader from '../FileReader'; 5 | 6 | const SolutionContainer = styled.div` 7 | padding: 10px; 8 | border: 1px solid gray; 9 | height: calc(100% - 30px); 10 | background-color: ${props => props.finalSolution ? "#dbe4d8" : "#e3e4e4"}; 11 | ` 12 | class Solution extends Component { 13 | constructor(props){ 14 | super(props); 15 | this.state = { 16 | solutionUsage: () =>
17 | } 18 | } 19 | 20 | componentDidUpdate(prevProps){ 21 | if (prevProps.location !== this.props.location){ 22 | this.loadSolution(); 23 | } 24 | } 25 | 26 | componentDidMount(){ 27 | this.loadSolution(); 28 | } 29 | 30 | componentDidCatch(e){ 31 | console.log('error', e); 32 | } 33 | 34 | loadSolution(){ 35 | FileReader.readFile(`${this.props.location}`) 36 | .then(solutionUsage => { 37 | this.setState({ solutionUsage }); 38 | }) 39 | } 40 | 41 | render(){ 42 | if (this.props.location) { 43 | const SolutionUsage = this.state.solutionUsage; 44 | return ( 45 | 46 | 47 | 48 | ) 49 | } 50 | return (
) 51 | } 52 | } 53 | 54 | Solution.propTypes = { 55 | //location of the example file to be rendered 56 | location: PropTypes.string.isRequired, 57 | 58 | finalSolution: PropTypes.bool 59 | } 60 | 61 | export default Solution; 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/setup/Tutorial.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import TutorialNavigation from './TutorialNavigation'; 5 | import { TutorialMetadataApi } from './TutorialMetadataApi'; 6 | import Markdown from './Markdown'; 7 | import AppShell from './AppShell'; 8 | 9 | const Container = styled.div` 10 | padding: 10px; 11 | margin: 10px 100px 10px 100px; 12 | `; 13 | 14 | const TutorialContainer = styled.div` 15 | margin-top: 100px; 16 | margin-bottom: 50px; 17 | `; 18 | 19 | const BottomNavigation = styled.div` 20 | margin-bottom: 30px; 21 | width: 100%; 22 | `; 23 | 24 | const Tutorial = ({ match }) => { 25 | const route = match.url; 26 | const currentTutorial = TutorialMetadataApi.getCurrentTutorial(route); 27 | const nextTutorial = TutorialMetadataApi.getNextTutorial(route); 28 | const previousTutorial = TutorialMetadataApi.getPreviousTutorial(route); 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default Tutorial; 46 | -------------------------------------------------------------------------------- /src/setup/TutorialMetadataApi.js: -------------------------------------------------------------------------------- 1 | import TutorialMetadata from './tutorialMetadata'; 2 | 3 | export class TutorialMetadataApi { 4 | static getTutorialMetadata() { 5 | return [...TutorialMetadata]; 6 | } 7 | 8 | static getCurrentTutorial(route) { 9 | return (TutorialMetadata.filter(d => d.route === route) || [])[0]; 10 | } 11 | 12 | static getCurrentTutorialIndex(route) { 13 | return TutorialMetadata.findIndex(d => d.route === route); 14 | } 15 | 16 | static getPreviousTutorial(route) { 17 | const currentIndex = TutorialMetadataApi.getCurrentTutorialIndex(route); 18 | if (currentIndex >= 1) { 19 | return TutorialMetadata[currentIndex - 1]; 20 | } 21 | } 22 | 23 | static getNextTutorial(route) { 24 | const currentIndex = TutorialMetadataApi.getCurrentTutorialIndex(route); 25 | if (currentIndex < TutorialMetadata.length - 1) { 26 | return TutorialMetadata[currentIndex + 1]; 27 | } 28 | } 29 | } 30 | 31 | export default new TutorialMetadataApi(); -------------------------------------------------------------------------------- /src/setup/TutorialNavigation.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import styled from 'styled-components'; 4 | 5 | const Container = styled.div` 6 | display: table; 7 | text-align: center; 8 | width: 100%; 9 | `; 10 | 11 | const Navigation = styled.div` 12 | width: 33%; 13 | display: table-cell; 14 | `; 15 | 16 | const linkStyle = { 17 | padding: 10, 18 | backgroundColor: "#3f51b5", 19 | color: "white", 20 | borderRadius: 5 21 | }; 22 | 23 | const TutorialNavigation = ({ previousTutorial, nextTutorial }) => { 24 | return ( 25 | 26 | 27 | {previousTutorial && ( 28 | 29 | ← {previousTutorial.displayName} 30 | 31 | )} 32 | 33 | 34 | 35 | Home 36 | 37 | 38 | 39 | {nextTutorial && ( 40 | 41 | {nextTutorial.displayName} → 42 | 43 | )} 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default TutorialNavigation; 50 | -------------------------------------------------------------------------------- /src/setup/renderer/CodeBlock.css: -------------------------------------------------------------------------------- 1 | .hljs-number { 2 | color: #dff162; 3 | } 4 | -------------------------------------------------------------------------------- /src/setup/renderer/CodeBlock.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Lowlight from 'react-lowlight'; 3 | import js from 'highlight.js/lib/languages/javascript'; 4 | import xml from 'highlight.js/lib/languages/xml'; 5 | import 'highlight.js/styles/default.css'; 6 | import 'highlight.js/styles/dracula.css'; 7 | import './CodeBlock.css'; 8 | 9 | Lowlight.registerLanguage('js', js); 10 | Lowlight.registerLanguage('xml', xml); 11 | Lowlight.registerLanguage('html', xml); 12 | 13 | function CodeBlock (props){ 14 | return ( 15 | 20 | ) 21 | } 22 | CodeBlock.defaultProps = { 23 | language: 'js', 24 | value: '' 25 | } 26 | export default CodeBlock; 27 | -------------------------------------------------------------------------------- /src/setup/renderer/HyperLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const StyledHyperlink = styled.a` 5 | border-bottom: 3px solid #3f51b5; 6 | &:hover { 7 | border-bottom: 5px solid #3f51b5; 8 | } 9 | `; 10 | 11 | export function HyperLink(props) { 12 | const { href, children } = props; 13 | return ( 14 | {children} 15 | ); 16 | } 17 | 18 | export default HyperLink; 19 | 20 | -------------------------------------------------------------------------------- /src/setup/tutorialMetadata.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 'Introduction', 4 | route: '/tutorial/react-introduction', 5 | displayName: '0 - React Introduction', 6 | markdownLocation: 'src/tutorial/build/00-ReactIntroduction.js' 7 | }, 8 | { 9 | id: 'HelloWorld', 10 | route: '/tutorial/hello-world', 11 | displayName: '1 - Hello World', 12 | markdownLocation: 'src/tutorial/build/01-HelloWorld.js', 13 | exercises: [{ 14 | tag: "exercise1", 15 | location: "src/exercise/01-HelloWorld.js", 16 | solutionLocation: "src/exercise/solution/01-HelloWorld-solution.js" 17 | }] 18 | }, 19 | { 20 | id: 'IntroToJSX', 21 | route: '/tutorial/intro-to-jsx', 22 | displayName: '2 - Introduction to JSX', 23 | markdownLocation: 'src/tutorial/build/02-IntroToJSX.js', 24 | exercises: [{ 25 | tag: "exercise1", 26 | location: "src/exercise/02-IntroToJSX.js", 27 | solutionLocation: "src/exercise/solution/02-IntroToJSX-solution.js" 28 | }] 29 | }, 30 | { 31 | id: 'PowerOfJSX', 32 | route: '/tutorial/power-of-jsx', 33 | displayName: '3 - Power of JSX', 34 | markdownLocation: 'src/tutorial/build/03-PowerOfJSX.js', 35 | exercises: [{ 36 | tag: "exercise1", 37 | location: "src/exercise/03-PowerOfJSX.js", 38 | solutionLocation: "src/exercise/solution/03-PowerOfJSX-solution.js" 39 | }] 40 | }, 41 | { 42 | id: 'Props', 43 | route: '/tutorial/understanding-props', 44 | displayName: '4 - Understanding Props', 45 | markdownLocation: 'src/tutorial/build/04-Props.js', 46 | exercises: [{ 47 | tag: "exercise1", 48 | location: "src/exercise/04-Props.js", 49 | solutionLocation: "src/exercise/solution/04-Props-solution.js" 50 | }] 51 | }, 52 | { 53 | id: 'State', 54 | route: '/tutorial/understanding-state', 55 | displayName: '5 - Understanding State', 56 | markdownLocation: 'src/tutorial/build/05-State.js', 57 | exercises: [{ 58 | tag: "exercise1", 59 | location: "src/exercise/05-State.js", 60 | solutionLocation: "src/exercise/solution/05-State-solution.js" 61 | }] 62 | }, 63 | { 64 | id: 'LifecycleMethods', 65 | route: '/tutorial/lifecycle-methods', 66 | displayName: '6 - Lifecycle Methods', 67 | markdownLocation: 'src/tutorial/build/06-LifecycleMethods.js', 68 | exercises: [{ 69 | tag: "exercise1", 70 | location: "src/exercise/06-LifecycleMethods.js", 71 | solutionLocation: "src/exercise/solution/06-LifecycleMethods-solution.js" 72 | }] 73 | }, 74 | { 75 | id: 'HandlingEvents', 76 | route: '/tutorial/handling-events', 77 | displayName: '7 - Handling Events', 78 | markdownLocation: 'src/tutorial/build/07-HandlingEvents.js', 79 | exercises: [{ 80 | tag: "exercise1", 81 | location: "src/exercise/07-HandlingEvents.js", 82 | solutionLocation: "src/exercise/solution/07-HandlingEvents-solution.js" 83 | }] 84 | }, 85 | { 86 | id: 'ComposingComponents', 87 | route: '/tutorial/composing-components', 88 | displayName: '8 - Composing Components', 89 | markdownLocation: 'src/tutorial/build/08-ComposingComponents.js', 90 | exercises: [{ 91 | tag: "exercise1", 92 | location: "src/exercise/08-ComposingComponents.js", 93 | solutionLocation: "src/exercise/solution/08-ComposingComponents-solution.js" 94 | }] 95 | }, 96 | { 97 | id: 'Capstone', 98 | route: '/tutorial/capstone', 99 | displayName: '9 - Capstone', 100 | markdownLocation: 'src/tutorial/build/09-Capstone.js', 101 | exercises: [{ 102 | tag: "exercise1", 103 | location: "src/capstone/Capstone.js", 104 | solutionLocation: "src/capstone/solution/Capstone.js" 105 | }] 106 | }, 107 | { 108 | id: 'Conclusion', 109 | route: '/tutorial/conclusion', 110 | displayName: '10 - Conclusion', 111 | markdownLocation: 'src/tutorial/build/10-Conclusion.js' 112 | } 113 | ] -------------------------------------------------------------------------------- /src/tutorial/00-ReactIntroduction.md: -------------------------------------------------------------------------------- 1 | [React](https://reactjs.org) is a flexible and declarative framework for building UI. React lets us build reusable "components" and compose those components to build any UI. Don't worry about what this all means right now, once you get through the tutorial you'll have a better idea. Let's start with Components. 2 | 3 | ### Component 4 | 5 | Components are building block of your UI - similar to directives in angular, or modules and widgets in other frameworks. Components in React are more or less self sufficient in that they constitute the presentation (HTML) as well as the behavior (eg. event handlers). They are also composable - meaning we can easily use one component within other component. So how do we create a Component? There are couple common ways how you can create a React component. 6 | 7 | #### 1. React.Component 8 | 9 | One way to create a React component is to create an ES6 `class` and extend `React.Component`. Each component created using this method should have a `render` function that returns what the DOM should look like if this component is rendered on the browser. 10 | 11 | ```jsx 12 | import React from 'react'; 13 | 14 | class Component extends React.Component { 15 | //needs render function 16 | render() { 17 | return ( 18 | //return should tell React what the DOM should look like 19 | //if this component is rendered in the browser 20 | ); 21 | } 22 | } 23 | ``` 24 | 25 | #### 2. function 26 | Another common way to create a React component is to create a simple function that takes in a parameter (we call `props`) and returns the exact same thing as above - what the DOM should look like if this component is rendered on the browser. 27 | 28 | ```jsx 29 | function Component(props) { 30 | return ( 31 | //return should tell React what the DOM should look like 32 | //if this component is rendered in the browser 33 | ); 34 | } 35 | ``` 36 | *Note: Components that are user-defined - meaning the components that you and I write and are not available in the React implementation itself - must have [first letter capital](https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized).* 37 | 38 | The above two approaches are identical except there are certain things that `React.Component` can do that the `function` cannot do but we will park that for now and come back to it later in this tutorial. 39 | 40 | Lets build our first `HelloWorld` React Component. 41 | -------------------------------------------------------------------------------- /src/tutorial/01-HelloWorld.md: -------------------------------------------------------------------------------- 1 | Our `HelloWorld` component will look like this: 2 | 3 | ```jsx 4 | import React from 'react'; 5 | 6 | function HelloWorld(props){ 7 | //function must return something 8 | return ( 9 | //return should tell React to render Hello World in the browser 10 | ); 11 | } 12 | ``` 13 | Now the question is what do we return from this function. 14 | 15 | The return of this function is telling React what the DOM should look like when this component is rendered on the browser. In case you're using `React.Component` approach (instead of `function` approach like above), it's what you return from `render` function that tells React what the DOM should look like when the component is rendered. 16 | 17 | In our case let's say we want to render a `div` element that has `Hello World` text like `
Hello World
` 18 | 19 | One way to tell React to display the above HTML is by using `React.createElement` function: 20 | 21 | ```jsx 22 | return React.createElement('div', null, 'Hello World'); 23 | ``` 24 | 25 | You should try this for yourself. Open the exercise file and edit the function to return a React element like above. Once you make the changes, save the file and you will see left panel updated with what React has rendered. 26 | 27 | 28 | 29 | Notice that `React.createElement` is a simple JavaScript function which takes three arguments. First argument is the element you want to render. In our case it's a `div` element. Second argument is any properties we want to pass to that element. In our case we are not passing anything so it's null. Third argument is the children for this component. In this case it's the text we want to display - `Hello World`. So with this we are telling React to render a div element like this: 30 | 31 | ```html 32 |
33 | Hello World 34 |
35 | ``` 36 | 37 | Congratulations, you have created your first Hello World React component. 38 | 39 | Now your inquisitive mind is probably asking - how in the world does React render this thing on the browser? 40 | 41 | ## Rendering 42 | 43 | Let's step back from React for a moment and think about how we can create the similar Hello World `div` using pure JavaScript. Yes pure JavaScript - without any frameworks. 44 | 45 | ### Good Ol' Days 46 | 47 | Let's imagine you have a barebone `html` file that looks like below. It has a `div` with id `root` inside `body`. Pretty simple. 48 | 49 | ```html 50 | 51 | 52 | 53 |
54 | 55 | 56 | ``` 57 | 58 | Now imagine inside the `div` with id `root` we want to render another `div` that says `Hello World`. The only catch is we want to do that programmatically using pure JavaScript. 59 | To achieve this we can probably do something like this: 60 | 61 | ```js 62 | //Create a div node and append Hello World text 63 | const helloWorldDiv = document.createElement('div'); 64 | helloWorldDiv.append('Hello World'); 65 | 66 | //Select the root node and append the div created above 67 | const root = document.getElementById('root'); 68 | root.appendChild(helloWorldDiv); 69 | ``` 70 | 71 | Here we are creating a `div` node with `Hello World` text and appending that `div` as a child of root `div`. 72 | 73 | We can actually write our entire application this way - creating elements, removing elements, appending elements, etc ourselves. As a matter of fact we did write applications this way before all these UI frameworks/libraries started to mushroom. 74 | 75 | ### Age of React 76 | A simple example like the one above are not that hard to write with pure JavaScript but once your application gets bigger, it gets messier. That's where libraries like React come to the rescue - they hide away from us the messier part of rendering on the browser. 77 | 78 | The Core React library itself doesn't really know how to render anything on the browser because it is designed to work in a web browser as well as native applications. Thus the job of rendering your component on the browser is done by another library provided by React team called `ReactDOM`. 79 | 80 | Now let's get back to the `HelloWorld` React component we created at the top of this page and see how we can use ReactDOM to render that component to the browser. 81 | 82 | ```jsx 83 | ReactDOM.render(HelloWorld, document.getElementById('root')) 84 | ``` 85 | 86 | Here we are calling a function called `render` on `ReactDOM` object. The first argument of the function is the component you want to render - in our case `HelloWorld`. Second argument is a document selector. ReactDOM appends the component we want to display (first argument) as a child of the node returned by the selector (second argument). 87 | 88 | Compare this solution to the pure JavaScript solution we looked at earlier. With pure JavaScript we were doing the DOM manipulation ourselves - creating the `div`, appending the text and appending the newly created `div` to the `div` with id `root` as its child. But with React we are not doing any DOM manipulation ourselves. Basically we are saying to React - 89 | 90 | > Hey React I have a component I want to render. I will tell you what the component should look like when it's rendered (remember this is what the return of the Component function tells). I will also tell you where to render this component (second argument we passed to `ReactDOM.render` function). But I don't want to get involved in DOM manipulation - I will let you do all the DOM manipulation yourself. You can call all these DOM api like `document.createElement`, `.append`, `.appendChild` etc. whenever you wish - I trust you and I don't care as long as I see on the browser what I expected to see. 91 | 92 | Ok then how does React do it internally? We won't go into all the details of it but briefly talk about one important part of React implementation called Virtual DOM. 93 | 94 | ### Virtual DOM 95 | [DOM (Document Object Model)](https://www.w3schools.com/js/js_htmldom.asp) is an object representation of the HTML. To see what it looks like open chrome dev tools (Right Click + Inspect) and type `console.dir(document)` and hit enter, you will see a JSON-like tree structure with fields and methods. React for it's part maintains a copy of this DOM - what's called a [virtual DOM](https://reactjs.org/docs/faq-internals.html), named so because it's not a real one, it's a virtual copy. 96 | 97 | Why does React hold a copy of the DOM? The main reason it maintains a virtual copy of the DOM is to improve performance of the application. 98 | Web applications these days are very complex. User interacts with the app or the app fetches data and based on that the DOM is updated so that users sees the effects of their interaction or new data. This updating of DOM, however, is an expensive operation - creating and removing DOM nodes (like we did with `document.createElement('div')` above) are expensive. So React optimizes this updating operations using virtual DOM. 99 | 100 | The way this roughly works is: when there's anything that will cause a UI to update (called re-render), React first updates its virtual DOM instead of real DOM. Then it compares the virtual DOMs (before and after the update). It has a heuristic algorithm to determine which piece of the DOM might have changed. Once it figures that out, it updates only the changed piece on the real DOM. Again the reason it does this is because updating the DOM is an expensive operation and it wants to optimize this piece so it only updates what is absolutely necessary instead of tearing down the entire DOM and recreating it. 101 | -------------------------------------------------------------------------------- /src/tutorial/02-IntroToJSX.md: -------------------------------------------------------------------------------- 1 | In the [previous section](/tutorial/hello-world) we created our first Hello World function component. Remember we returned `React.createElement` from the function to tell React what the DOM should look like when we render this component. Another alternative way of telling React what the DOM should look like is by using JSX. JSX is a very common and recommended way (preferred over `React.createElement` syntax in most cases) to write React code. JSX is a funny looking syntax though - it's not purely HTML, it's not purely JavaScript. But it's an extension of JavaScript where you can write HTML like syntax with full power of JavaScript. For example, the equivalent of return statement we saw in [previous page](/tutorial/hello-world) (using `React.createElement`) in JSX would be: 2 | 3 | ```jsx 4 | return ( 5 |
Hello World
6 | ) 7 | ``` 8 | 9 | Instead of returning JavaScript code, it's returning HTML-like code (it's not HTML) and notice it's not a string. Wait, what?!! Welcome to JSX! 10 | 11 | You don't trust that this weird syntax works, do you? Open the exercise file and edit the return statement with the JSX and save to see the magic happen! 12 | 13 | 14 | 15 | Although you write HTML looking syntax, your JSX code is compiled into a JavaScript function like the one we saw in the previous page. The above JSX code is compiled into: 16 | 17 | ```jsx 18 | return React.createElement('div', null, 'Hello World'); 19 | ``` 20 | 21 | So writing above JSX code or `React.createElement` code will generate exactly same output on the browser. You can definitely write all your React code using `React.createElement` and not ever care about JSX. But it's gonna get pretty complicated pretty soon. Imagine writing all the nested `React.createElement` functions for generating simple HTML like below: 22 | 23 | ```html 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
27 | Col
Cell
36 | ``` 37 | You'd have to write something like this, which is not very pretty: 38 | ```js 39 | React.createElement('table', null, 40 | [ 41 | React.createElement('thead', null, 42 | React.createElement('th', null, 43 | React.createElement('td', null, 'Col') 44 | ) 45 | ), 46 | React.createElement('tbody', null, 47 | React.createElement('tr', null, 48 | React.createElement('td', 'null', 'Cell') 49 | ) 50 | ) 51 | ] 52 | ``` 53 | So in essence JSX is nothing more than a syntactic sugar for complicated `React.createElement` functions to make React code more elegant and readable. 54 | -------------------------------------------------------------------------------- /src/tutorial/03-PowerOfJSX.md: -------------------------------------------------------------------------------- 1 | In the previous page we looked at JSX syntax where we displayed a string `Hello World` inside the `div` element. What's the big deal! What else can it do for us? 2 | 3 | Since JSX is technically JavaScript it is pretty powerful in many senses. It can do everything that JavaScript can do. 4 | 5 | If you want to execute any JavaScript code within JSX then you surround your JavaScript code with curly braces `{ //JavaScript code }` and put it anywhere in JSX. It will evaluate your code every time it renders the component to find out what it should render on the browser. 6 | 7 | For example let's imagine we want to display a company profile information and a Company ticker. And imagine the Company ticker is stored on some variable somewhere and the company profile information is stored in another variable as an object. In that case we might write: 8 | 9 | ```jsx 10 | function CompanyProfile(props) { 11 | //ticker and companyProfileInfo stored in a variable 12 | const ticker = 'AAPL'; 13 | const companyProfileInfo = { 14 | 'Company Name': 'Apple Inc.', 15 | 'Exchange': 'Nasdaq', 16 | 'Sector': 'Technology', 17 | 'Industry': 'Computer Hardware', 18 | 'CEO': 'Timothy D. Cook' 19 | }; 20 | return ( 21 |
22 |
Profile of: {ticker}
23 |
24 | { 25 | {/*This is JavaScript code inside the curly braces*/} 26 | Object.keys(companyProfileInfo) 27 | .map((key, index) => { 28 | return
{key}: {companyProfileInfo[key]}
29 | }) 30 | } 31 |
32 |
33 | ) 34 | } 35 | ``` 36 | 37 | The HTML output of the above Component when it's rendered will be: 38 | 39 | ```html 40 |
41 |
Profile of: AAPL
42 |
43 |
Company Name: Apple Inc.
44 |
Exchange: Nasdaq
45 |
Sector: Technology
46 |
Industry: Computer Hardware
47 |
CEO: Timothy D. Cook
48 |
49 |
50 | ``` 51 | 52 | Well that's a handful, so let's review what's happening here. We have a `ticker` variable with a value `AAPL` and an object `companyProfileInfo` that has company profile. Inside the JSX (inside the `return` statement) we have a `div` enclosing everything. In JSX, **a component must return one and only one enclosing tag**. That tag can have as many children as it wants. 53 | ```jsx 54 | // ❌ This is illegal in React since the return has more than one tag. 55 | return ( 56 |
57 |
58 | ) 59 | 60 | // ✅ This is perfectly legal because there is just one enclosing tag and 61 | // it can have as many children as it likes 62 | return ( 63 |
64 |
65 |
66 |
67 |
68 |
69 | ) 70 | 71 | // ✅ If you don't want to wrap your component with some enclosing tag like `div` 72 | // you can wrap everything with `React.Fragment` which is an empty tag provided by React 73 | return ( 74 | 75 |
76 |
77 |
78 | ) 79 | ``` 80 | 81 | Going back to the company profile example, the first child of the enclosing `div` is another `div`. Inside that `div` we used curly braces to display the `ticker` alongside `Profile of:`. Remember curly braces is how we inject JavaScript code inside JSX. So here the `ticker` variable would be evaluated and rendered inside that `div` tag. Then we have another `div` as a second child of the enclosing parent. Inside this `div` we again have curly braces and we execute some JavaScript code. In this case we mapped each key of the `companyProfileInfo` object to a `div` element. The content of this `div` is again evaluated using another curly braces like: `{key} : {companyProfileInfo[key]}`. What we did here is that we told React that for each key of the `companyProfileInfo` object we want to render a `div` whose content would be the `key` followed by a colon `:` followed by the corresponding value for the key on the object (`companyProfileInfo[key]`). 82 | 83 | Let's write some code here to hit the nail on the head. Please open the exercise file and follow the instructions. 84 | 85 | 86 | 87 | Key takeaways: 88 | - Components must return only one tag. This tag can have as many children as it likes. Instead of a tag, it can however return a string or null. 89 | - You can run any JavaScript code inside the `return` using curly braces `{//run any JavaScript}`. 90 | - Outside of the `return` it's exactly like any other JavaScript class or function. You can do whatever you desire to do. 91 | 92 | ### Differences with HTML 93 | 94 | There are some commonly used things that are slightly different in JSX than in HTML. 95 | 96 | - Styles 97 | 98 | In HTML, styles are passed as string. The css properties are kebab-cased. 99 | 100 | ```html 101 |
102 | ``` 103 | 104 | In JSX, styles are passed as an object. The css properties are camelCased. 105 | 106 | ```jsx 107 |
108 | ``` 109 | 110 | - Class 111 | 112 | In HTML class attribute is passed as string. 113 | 114 | ```html 115 |
116 | ``` 117 | 118 | In JSX also class attribute is passed as string but instead of calling it `class` we call it `className`. That's because JSX is extension of JavaScript and "class" is a reserved keyword in JavaScript. 119 | 120 | ```jsx 121 |
122 | ``` 123 | - Event Handler 124 | 125 | In HTML event handler attribute is all lower cased and the handlers are passed as string. 126 | 127 | ```html 128 |
129 | ``` 130 | 131 | In JSX, event handler are camelCased and instead of string we pass the actual function. 132 | 133 | ```jsx 134 |
135 | ``` 136 | 137 | We will look more into [event handler](/tutorial/handling-events) later in the tutorial. 138 | -------------------------------------------------------------------------------- /src/tutorial/04-Props.md: -------------------------------------------------------------------------------- 1 | Let's continue with the earlier example where we displayed company profile of `AAPL`. Let's say I want to display the Company Profile for `FB` at some other place in the application. Since we have two variables `ticker` and `companyProfileInfo` hard-coded inside this component, should we copy and paste the entire component to some other place and replace the `ticker` and `companyProfileInfo` to be that of `FB`? 2 | 3 | Well, no. Remember that React is about building reusable Components. So we want to build a component called `CompanyProfile` that can be reused for any company ticker. 4 | 5 | Let's look at the `CompanyProfile` component again. Here we have two variables `ticker` and `companyProfileInfo`. So what if instead of hard coding the values of these two variables here inside the component, we could pass those values to this component instead? 6 | 7 | If we were to pass these values to this component from outside, then this Component will not be tied to one Company ticker. We can pass in `XYZ` and it's profile info to this component and it should be able to render the profile for `XYZ` or any other company for that matter. This component becomes truly reusable. Wonderful, that's what we want. 8 | 9 | ```jsx 10 | function CompanyProfile(props) { 11 | //Instead of storing these variables we want to pass 12 | //these values to this component from outside. 13 | const ticker = //pass the value for this variable from outside 14 | const companyProfileInfo = //pass the value for this variable from outside 15 | return ( 16 |
17 |
Profile of: {ticker}
18 |
19 | { 20 | Object.keys(companyProfileInfo) 21 | .map((key, index) => { 22 | return
{key}: {companyProfileInfo[key]}
23 | }) 24 | } 25 |
26 |
27 | ) 28 | } 29 | ``` 30 | 31 | Well then how do we pass these values to a component from outside? That's where `props` comes in. In React lingo, `props` is something that the component is passed by the user of that component (parent component passes `props` to child component). 32 | You can pass anything as a prop: `function`, `object`, `boolean`, `string`, `number`, etc. Here's an example of a Component passing the `props` to its children. 33 | 34 | ```jsx 35 | function Children(props) { 36 | return ( 37 |
{props.textToDisplay}
38 | ) 39 | } 40 | 41 | function Parent(props) { 42 | return ( 43 | 44 | ) 45 | } 46 | ``` 47 | There are couple things going on here. First - remember on the [first page of this tutorial](/tutorial/react-introduction) we said that we can compose components (we can use one component inside the other)? Well that's what we are doing here. `Parent` component uses `Children` component inside it's return. 48 | 49 | Second - if you inspect the above code snippet carefully, we see that when the `Parent` uses the `Children` (inside `return`) it's also passing something called `textToDisplay` with some value `Hello`. We call this "passing the props". So `Parent` is passing a `props` called `textToDisplay` to the `Children`. How, then, does the `Children` use the value that the `Parent` passes down to it? 50 | 51 | 1. Component created with function 52 | 53 | If you created your component as a `function` like we did here, all the `props` its `Parent` passed down will be accessible through the argument. If you look at the `Children` above it's using `props.textToDisplay` inside the `div`. All the `props` passed to the `Children` are passed as this single `props` argument. For example, if the `Parent` had passed a props called `defaultValue`, the `Children` would access it as `props.defaultValue`. 54 | 55 | 2. Component created as React.Component 56 | 57 | If you created your Component by extending `React.Component` then all the `props` would be available as `this.props`. The equivalent of above `Children` function using `React.Component` would look like this: 58 | 59 | ```jsx 60 | class Children extends React.Component { 61 | render(){ 62 | return ( 63 |
{this.props.textToDisplay}
64 | ) 65 | } 66 | } 67 | ``` 68 | 69 | Now that we know what `props` are, lets do some exercise and see how we can make `CompanyProfile` component reusable. 70 | 71 | 72 | 73 | 74 | One thing you must remember regarding `props` is that you should **never** mutate `props` - React will complain if you do. This is something given to the component by it's parent - accept with love and don't try to mess around with things to make your parent angry! 75 | 76 | ```jsx 77 | function Children(props) { 78 | //❌ NEVER DO THIS 79 | props.textToDisplay = 'I want to mutate props' 80 | return ( 81 |
{props.textToDisplay}
82 | ) 83 | } 84 | ``` 85 | 86 | ### PropTypes 87 | In many cases it's better for a component to clearly define a contract regarding the `props` it can accept - data type, data structure, if the props is required etc. 88 | There are couple obvious benefits of this: 89 | - React can enforce type checking to avoid many bugs arising from parents passing props with a type that's different from what the children expects (ex. parent passing `string` when children expects an `object`). 90 | - If you are writing components that will be used by different people at different parts of the application, it's always useful for those users to know what are the props they can pass, what is the expected structure of the props etc. 91 | 92 | To define this contract - first you need to add `prop-types` as a [project dependency (provided by React team)](https://www.npmjs.com/package/prop-types) and you need to define a special property called `propTypes` in your component. 93 | 94 | ```jsx 95 | import React from 'react'; 96 | import PropTypes from 'prop-types'; 97 | 98 | class SoftwareEngineer extends React.Component { 99 | render(){ 100 | return (...) 101 | } 102 | } 103 | 104 | //defines "propTypes" property in this component 105 | SoftwareEngineer.propTypes = { 106 | name: PropTypes.string.isRequired, //expects string and is required 107 | hobbies: PropTypes.arrayOf(PropTypes.string), //expects array of string 108 | address: PropTypes.shape({ 109 | street: PropTypes.string, 110 | city: PropTypes.string 111 | }) //must be an object with 'street' and 'city' fields 112 | } 113 | ``` 114 | Here we have defined the `propTypes` property and assigned an object. Each key in this object represents the name of the `props` the user of this component can pass. The value defines the "type" of the `props` - you know: `string`, `number`, `array`, etc. All `props` are optional (user of the component doesn't have to pass them) except the one that has `.isRequired`. Here's a quick explanation on three `props` defined above: 115 | - `name` - It expects the value of this `props` to be a `string` and it's required because, well, it has `.isRequired`. 116 | - `hobbies` - It's optional but if passed it must be an array of strings. 117 | - `address` - It's also optional but if passed it must be an object with two fields - `street` and `city` - and both must be string. 118 | 119 | These are just some examples of what you can do to enable type checking. There are plenty more types you can define - please check out [the documentation](https://reactjs.org/docs/typechecking-with-proptypes.html#proptypes) for more. 120 | 121 | ### Default Props 122 | In some cases you might want to define a default value for a `props` in case it is not passed to you. 123 | You can use `defaultProps` property to define your defaults. With this you're basically saying - "if someone doesn't pass me a value for a `props` that I'm expecting, then I want the value of that `props` to be what I have defined in the `defaultProps`". For example - for the above component we can define `defaultProps` as follows: 124 | 125 | ```jsx 126 | import React from 'react'; 127 | import PropTypes from 'prop-types'; 128 | 129 | class SoftwareEngineer extends React.Component { 130 | render(){ 131 | //if this props is not passed, it will print default value as defined by `defaultProps` 132 | console.log(this.props.hobbies); 133 | 134 | //if this props is not passed, it will print `undefined` because we haven't defined any default value for this props 135 | console.log(this.props.address); 136 | return (...) 137 | } 138 | } 139 | 140 | //defines "defaultProps" property in this component 141 | SoftwareEngineer.defaultProps = { 142 | hobbies: ['Writing React code'] 143 | } 144 | ``` 145 | 146 | Let's say if the user of this component doesn't pass any value for `hobbies`, then it will be defaulted to `['Writing React code']`. 147 | And if the user of the component doesn't pass any value for `address` then it will resolve to `undefined` because we haven't defined the default value for it. 148 | -------------------------------------------------------------------------------- /src/tutorial/05-State.md: -------------------------------------------------------------------------------- 1 | React Components can have local state. This state is not shared with the component's parent or the component's child by default. It is fully owned and controlled by the component itself. 2 | 3 | Remember when we looked at different ways of creating React component in the first page of this tutorial, we said there are things that component extending `React.Component` can do that a `function` component cannot do? Well state is one of them. `React.Component` can have states but `function` cannot have states. 4 | 5 | *Note: There's a new feature in React called hooks that let's us use state within function component but that's still in alpha so if we want to use state right now on a component we still need to use `React.Component`.* 6 | 7 | Below is a simple component that has a state. 8 | 9 | ```jsx 10 | class Component extends React.Component { 11 | constructor(props){ 12 | super(props); 13 | this.state = { 14 | counter: 0 15 | } 16 | } 17 | 18 | render() { 19 | return
{this.state.counter}
20 | } 21 | } 22 | ``` 23 | 24 | `state` is just an object. If you notice the constructor, we initialized the state with `{counter: 0}`. And we used the state inside the `return` of the `render` function as `{this.state.counter}`. 25 | 26 | We initialized the `state` in the constructor, but how do we update it? For example in the above example, how do we change the `counter` to let's say 1? For that React provides a function called `setState`. You should **always** use the `setState` function to change `state` and **never** mutate it directly. 27 | 28 | ```jsx 29 | //❌ NEVER DO THIS 30 | this.state.counter = 2; 31 | 32 | // ✅ ALWAYS DO THIS 33 | this.setState({ 34 | counter: 2 35 | }); 36 | ``` 37 | 38 | If `state` is just an instance variable in the component, can we call it some other name? And why do we **have** to use `setState`? Well no we cannot call it by some other name, and we have to use `setState` to update the `state` mainly because React understands `state`. When `state` of your component changes, React re-renders your component (by re-render I mean calls the `render` function again to see if the DOM will change as a result of change in `state`). This is fundamental to the declarative nature of React. 39 | 40 | ### setState 41 | 42 | Let's look deeper into `setState` function. The first argument of `setState` function can take either a new state object or a function. It also has an optional second argument, a callback which is executed when the state is updated. 43 | 44 | ```jsx 45 | setState(newState || function, optional callback) 46 | ``` 47 | 48 | The way `setState` updates the state is: 49 | - If the first argument of the `setState` function is an object, it merges the current `state` object with whatever you passed to the `setState` function. For example: 50 | 51 | ```jsx 52 | state = { a: 1, b: 2, c: 3} //current state 53 | this.setState({ a: 3 }); //we call setState with just one key value pair 54 | 55 | //it will **merge** the initial state with the object passed to setState 56 | //as a result only the value for that one key is updated 57 | state = { a: 3, b: 2, c: 3 } //state after setState is flushed 58 | ``` 59 | 60 | - If the first argument is a function, then it first executes the function by passing the current `state` as it's argument. The function must return an object. It then merges this output with the current `state` just like it did above. For example: 61 | 62 | ```jsx 63 | state = { a: 1, b: 2, c: 3} //initial state 64 | //we called setState with a function 65 | this.setState(currentState => ({ 66 | a: currentState.a + 1 67 | })); 68 | //it executes the function by passing the current state object as argument. 69 | //since currentState.a is 1, function returns { a: 2 } 70 | //it now merges this returned object with the original state 71 | state = { a: 2, b: 2, c: 3 } //state after setState is called 72 | ``` 73 | 74 | One thing you must know about `setState` function is that it may be asynchronous. So **do not** rely on it to update the state immediately. This is not a bug, it's by design. If you want to read up on the design decision behind `setState` call being asynchonous, here's a [nice explanation](https://github.com/facebook/react/issues/11527#issuecomment-360199710). 75 | 76 | Since `setState` can be asynchronous below code will not give you the right result because by the time we `console.log` the `state.counter` value, it won't be updated. 77 | 78 | ```jsx 79 | //❌ WRONG RESULT. Do not rely on setState to be synchronous 80 | console.log(this.state.counter);//prints 0 81 | this.setState({ 82 | counter: this.state.counter + 1 83 | }); //this is asynchronous call 84 | console.log(this.state.counter);//still prints 0 85 | ``` 86 | 87 | Also if you want to update `state` using the current state value, **always** use the updater function inside `setState` instead of passing object. For example below code will not work. 88 | 89 | ```jsx 90 | //❌ DONT DO THIS 91 | //If you are using the current state value to update the state, never use this.state directly inside setState 92 | console.log(this.state.counter); //prints 0 93 | this.setState({ counter: this.state.counter + 1 }); 94 | this.setState({ counter: this.state.counter + 1 }); 95 | this.setState({ counter: this.state.counter + 1 }); 96 | //the state will be 1 when all of the calls are flushed 97 | //because since the calls were asynchronous, this.state.counter 98 | //on all three calls were 0 and adding 1 resulted in 1. 99 | 100 | 101 | //✅ ALWAYS DO THIS 102 | //If you are using current state value to update the state, always use updater function 103 | console.log(this.state.counter); //prints 0 104 | this.setState((state) => ({ counter: state.counter + 1}) ); 105 | this.setState((state) => ({ counter: state.counter + 1}) ); 106 | this.setState((state) => ({ counter: state.counter + 1}) ); 107 | //this is guaranted to work! 108 | //when all three calls are flushed the value of 109 | //this.state.counter will be 3 110 | ``` 111 | 112 | Let's look at an exercise. Click the '+', '-' on the right side to see the expected behavior. Now please open the exercise file and make changes as instructed in the exercise file to achieve the expected behavior. 113 | 114 | 115 | 116 | I know we discussed several things about `state` and it must be overwhelming. Let's just recap the rules: 117 | - Never mutate `this.state` directly. Always use `this.setState` to update the `state`. 118 | - If your new `state` doesn't depend on the old `state` then you can use `this.setState(object)` construct. 119 | - If your new `state` depends on the old `state` then use `this.setState(function(currentState){ .. })` construct. 120 | 121 | #### Props and State 122 | Since we now have looked into both `state` and `props` how are they different and how are they similar? 123 | 124 | The difference between `state` and `props` is that `state` is owned by the component itself while `props` is something that is passed down to the component by it's parent. 125 | 126 | And the similarity (sort of) is that React automatically re-renders your component when either the component's `state` changes or when the component's `props` changes. 127 | 128 | Your component's `render` function is a function of both `state` and `props` meaning it defines what your component should look like given the `state` and `props`. It should be pure function in a sense that if the component has same `state` and `props` it should render exactly same content no matter how many times it's called and shouldn't have any side effects. 129 | -------------------------------------------------------------------------------- /src/tutorial/06-LifecycleMethods.md: -------------------------------------------------------------------------------- 1 | React provides a way to take some actions on different lifecycle phases of the component. There are several benefits to that. For example, we may want to fetch some data when a component is rendered, or clean up some resources before the component is removed from the DOM. React calls these lifecycle methods (if we have defined them) during these lifecycle phases. You are not required to implement any of these lifecycle methods, you implement only those that you need on your particular component. 2 | 3 | ```jsx 4 | class ComponentWithLifecycle extends React.Component { 5 | constructor(props){ 6 | super(props); 7 | } 8 | 9 | componentDidMount(){ 10 | //This will be called after the component is mounted to the DOM 11 | } 12 | 13 | componentDidUpdate(prevProps, prevState){ 14 | //This will be called after the component is updated 15 | //Remember component can only be updated when the state changes 16 | //or the props changes 17 | } 18 | 19 | componentWillUnmount(){ 20 | //This will be called right before this component is unmounted from 21 | //the DOM 22 | } 23 | } 24 | ``` 25 | 26 | Lets look at some common use cases where these lifecycle methods comes in handy. 27 | 28 | Let's consider the `CompanyProfile` example we looked at earlier where we were displaying the `ticker` and the `companyProfileInformation`. In the example, the parent of the component passed both `ticker` and `companyProfileInformation` as `props`. But let's assume that the parent only has the `ticker` information, which it will pass as `props`, but it doesn't have the profile information data. Assume there's an API that the `CompanyProfile` component can use to fetch the profile information data. This is a very common use case. So how/when should the `CompanyProfile` component fetch the data? 29 | 30 | `componentDidMount` is the right lifecycle method to make the network call (fetch data using the API). This method is called only once - when the component is mounted on the DOM. After the API returns the data, we can set the data to the `state` and use `this.state.companyProfileInformation` instead of `this.props.companyProfileInformation` inside the `render` function. 31 | Remember that in this case the parent didn't pass the `companyProfileInformation` props but instead `CompanyProfile` fetched that data itself and stored in the `state`. 32 | 33 | ```jsx 34 | import DataApi from '../api'; 35 | 36 | class CompanyProfile extends React.Component { 37 | componentDidMount() { 38 | DataApi.getCompanyProfile(this.props.ticker) 39 | .then(profile => { 40 | this.setState({ 41 | companyProfileInformation: profile 42 | }) 43 | }) 44 | } 45 | } 46 | ``` 47 | 48 | Let's do the same exercise. Please open the exercise file and make the change as instructed on the file. 49 | 50 | 51 | 52 | That works for the initial value of `ticker` passed by parent because `componentDidMount` is called only once. But what happens when the `props` changes (meaning the parent component passes a new `ticker` value)? How/when would `CompanyProfile` component know to fetch data again for the new `ticker`? In that case you can use `componentDidUpdate`. This lifecycle method is called every time component is updated. Remember component is updated every time the `props` or the `state` changes. 53 | 54 | You're probably thinking - 'This doesn't sound good'. If you were to fetch the data inside `componentDidUpdate` (which would be called every time your component updates), your component will make a lot of repetitive network calls to get the profile information for same ticker because the component might have updated, not just when `ticker` props changed but when any other `props` changes or some other `state` changes. 55 | Well you're thinking correctly. That's the reason why you should **always** check if the `props` you are interested in changed (in this case `ticker` changed) before making the network request. 56 | 57 | ```jsx 58 | import DataApi from '../api'; 59 | 60 | class CompanyProfile extends React.Component { 61 | //when react calls componentDidUpdate, it gives as argument the value of 62 | // props and state before the update happened so you can do the comparision 63 | componentDidUpdate(prevProps, prevState) { 64 | //always be defensive, otherwise you will make a lot of 65 | //unnecessary network calls 66 | //in this case we only make the network call if the 67 | //ticker props before and after the component updated are not same 68 | if (prevProps.ticker !== this.props.ticker) { 69 | DataApi.getCompanyProfile(this.props.ticker) 70 | .then(profile => { 71 | this.setState({ 72 | companyProfileInformation: profile 73 | }) 74 | }) 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | These are just some common use cases when you want to use the lifecycle methods provided by React. Please refer to the 81 | [React documentations](https://reactjs.org/docs/react-component.html#the-component-lifecycle) 82 | for an extensive list of all the lifecycle methods available. 83 | 84 | Also note that there are some lifecycle methods that were available as part of earlier versions of React (16 and earlier), and they will be deprecated as part of version 17.0, so **DO NOT** use them: 85 | 86 | - componentWillMount 87 | - componentWillUpdate 88 | - componentWillReceiveProps 89 | 90 | Please refer to this [blog post](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html) to understand why these lifecycle methods are being deprecated and also more about some best practices regarding usage of lifecycle methods. 91 | -------------------------------------------------------------------------------- /src/tutorial/07-HandlingEvents.md: -------------------------------------------------------------------------------- 1 | Handling events in JSX is pretty similar to handling events in actual DOM. 2 | For example if we have a `button` element and we want to pass an event handler for a `click` event, we can pass a props called `onClick` to the button element. 3 | 4 | 5 | ```jsx 6 | function ClickableButton(props){ 7 | //callback function that's called when button is clicked 8 | const handleClick = () => { alert('Clicked') } 9 | return ( 10 | 11 | ) 12 | } 13 | ``` 14 | 15 | Notice there are couple difference between how events are handled in HTML vs JSX. 16 | 17 | 1. With HTML we would have an attribute called `onclick` (all lowercased), but with JSX have a camelCased `props` called `onClick`. In JSX always use camelCase instead of lowercase like in HTML. 18 | Ex: 19 | - `onclick` => `onClick` 20 | - `onmouseover` => `onMouseOver` 21 | - `onselect` => `onSelect` 22 | - `onchange` => `onChange` 23 | 24 | 2. With HTML we would pass a string as a value of the attribute, but in JSX we pass the actual function. For example compare the above `button` in JSX to the following HTML equivalent: 25 | 26 | ```html 27 | 28 | ``` 29 | 30 | ### Synthetic events 31 | 32 | When React invokes the event-handler, it provides a [SyntheticEvent](https://reactjs.org/docs/events.html) as an argument. Not to worry about any of the details, it's a wrapper around browser's native event and it has the same interface as the native event. 33 | 34 | ```jsx 35 | function InputComponent(props){ 36 | //callback function that's called when input changes 37 | //It gets synthetic event as an argument 38 | //SyntheticEvent has interface just like the native browser event 39 | //In this case - to get the value of the input we did e.target.value 40 | const handleChange = (e) => { alert(e.target.value) } 41 | return ( 42 | 43 | ) 44 | } 45 | ``` 46 | ### Function Binding 47 | 48 | One gotcha when using event handlers with components created as `ES6` classes (by extending `React.Component`) is the method binding. This has nothing to do with React or JSX - it's a new JavaScript feature in `ES6` and trips even many experienced folks. 49 | 50 | Class functions in `ES6` are not bound to anything by default. For example take below example of `Input` component. Here we just have an input element whose value is assigned to `this.state.inputValue`, and we are handling the `onChange` event on this `input` by passing our `this.handleChange` function. Inside the `handleChange` function we are calling `setState` with the new value typed by the user. Now if we run this code it will error out saying something like "Cannot read property setState of undefined". Weird huh? The reason is because `handleChange` is not bound to anything. So `this` inside `handleChange` is `undefined`. 51 | 52 | ```jsx 53 | class Input extends React.Component { 54 | 55 | handleChange(e){ 56 | this.setState({ 57 | inputValue: e.target.value 58 | }); 59 | } 60 | 61 | render(){ 62 | return( 63 | 64 | ) 65 | } 66 | } 67 | ``` 68 | 69 | To fix above issue we just need to `bind` the function to proper `this`. The common practice is to do that in the constructor. So below we added a line in a constructor `this.handleChange = this.handleChange.bind(this)` to bind the `handleChange` function and it would work like a charm. 70 | 71 | ```jsx 72 | class Input extends React.Component { 73 | constructor(props){ 74 | super(props); 75 | 76 | //bind handleChange function to proper this 77 | this.handleChange = this.handleChange.bind(this); 78 | } 79 | 80 | handleChange(e){ 81 | this.setState({ 82 | inputValue: e.target.value 83 | }); 84 | } 85 | 86 | render(){ 87 | return( 88 | 89 | ) 90 | } 91 | } 92 | ``` 93 | 94 | Let's write some code. Please open the exercise file and follow the instruction on the file. 95 | 96 | 97 | 98 | Please read [this article](https://cmichel.io/es6-class-methods-differences/) to get more idea on the `ES6` binding dilemma. 99 | -------------------------------------------------------------------------------- /src/tutorial/08-ComposingComponents.md: -------------------------------------------------------------------------------- 1 | One of the most important characteristics of React is that it lets you compose different components to build your UI. 2 | 3 | Let's think about a use case. Imagine we want to build a reusable `Dialog` component. Assume it's a simple dialog component - nothing fancy. When this component is rendered we want to display some content within a modal window. 4 | 5 | ```jsx 6 | class Dialog extends React.Component { 7 | render(){ 8 | return ( 9 |
10 | //display content of dialog 11 | //????? but we don't know what to display 12 |
13 | ) 14 | } 15 | } 16 | ``` 17 | That sounds simple except we don't know what the content of the dialog is going to be. Imagine at one place we might want to display an employee contact within a dialog and at some other place we might want to display a grid. This component itself has no knowledge of it beforehand. And if we wanted to make this component truly reusable it **should not** have any knowledge of it beforehand. 18 | 19 | The job of the `Dialog` component is to render a modal window but it shouldn't care about the content - we should be able to use this component at different places to render different things. 20 | 21 | How do we tell this dialog to display different things? 22 | 23 | There are a couple of common patterns used in React to do this: 24 | 25 | 1. props.children 26 | 27 | One way to achieve this goal of reusing `Dialog` anywhere we like without `Dialog` having to know what it's displaying, is by using the `children` props. Let's look at one potential usage of `Dialog` component below to understand this: 28 | 29 | ```jsx 30 | //Displays EmployeeProfile inside a Dialog 31 | class EmployeeProfileDialog extends React.Component { 32 | render(){ 33 | //We have passed a child component inside the Dialog 34 | return ( 35 | 36 | 37 | 38 | ) 39 | } 40 | } 41 | ``` 42 | Here we have a component called `EmployeeProfile` (any other valid React component would also work here) as a "child" of `Dialog`. When we pass a "child" to a component it will be available inside that component as a `props` called `children`. So in this case, `EmployeeProfile` will be available inside the `Dialog` component as `props.children`. We can rewrite the render function of `Dialog` so that it renders `props.children`: 43 | 44 | ```jsx 45 | class Dialog extends React.Component { 46 | render(){ 47 | //Render the children provided to this component 48 | //Here we don't really care what the children is 49 | return ( 50 |
51 | {this.props.children} 52 |
53 | ) 54 | } 55 | } 56 | ``` 57 | Think about what we did here. The `Dialog` component really doesn't know what it's `children` would be beforehand. Whoever is using this `Dialog` component can pass in any `children` that they like. Within the `render` function `Dialog` says "Hey, I'll display anything my user passes me as `children`, I don't need to know what that is." 58 | 59 | This my friend is composition and this is mighty powerful if you want to write reusable components. 60 | 61 | 2. render props 62 | 63 | Another pattern to achieve a similar thing in React is by using what's known as the "render props" pattern. 64 | 65 | So now let's take the same dialog example, but let's make it more sophisticated. Imagine the dialog has a header, body and footer section. We want the user of this component to be able to display anything they like within either of those three sections. 66 | 67 | ```jsx 68 | class Dialog extends React.Component { 69 | render(){ 70 | return ( 71 |
72 |
73 | { 74 | /** user of this component should be able to display any header inside here */ 75 | } 76 |
77 |
78 | { 79 | /** user of this component should be able to display any body inside here */ 80 | } 81 |
82 |
83 | { 84 | /** user of this component should be able to display any footer inside here */ 85 | } 86 |
87 |
88 | ) 89 | } 90 | } 91 | ``` 92 | 93 | The `props.children` approach we used above might not work well here. All the children we pass as "children" would be available as `props.children` and it might be little cumbersome to split the children into our three segments - "header", "body" and "footer". Wouldn't it be nice if we could tell the `Dialog` component explicitly what it should render within each "header", "body" and "footer" section? 94 | 95 | We can use the "render props" pattern to do exactly that. All this means is that we can pass three render functions as `props` that tells the dialog what it should render inside each segment. For example, lets look at a usage: 96 | 97 | ```jsx 98 | class EmployeeProfileDialog extends React.Component { 99 | render(){ 100 | return ( 101 | } 103 | renderBody={() => } 104 | renderFooter={() => } 105 | /> 106 | ) 107 | } 108 | } 109 | ``` 110 | Here, all we are really doing is passing three different `props` called `renderHeader`, `renderBody` and `renderFooter`. They are all just functions, and when called will return the appropriate React component to display. Now we can edit the `Dialog` component to get it working as expected: 111 | 112 | ```jsx 113 | class Dialog extends React.Component { 114 | render(){ 115 | return ( 116 |
117 |
118 | {this.props.renderHeader()} 119 |
120 |
121 | {this.props.renderBody()} 122 |
123 |
124 | {this.props.renderFooter()} 125 |
126 |
127 | ) 128 | } 129 | } 130 | ``` 131 | 132 | Here, instead of displaying `this.props.children` like we did previously, we executed the different `props` functions as appropriate on the different sections. Whatever is returned from that function during runtime will be displayed within that section. The `Dialog` component remains completely agnostic to what it's displaying. 133 | 134 | Let's do some exercises shall we? Please open the exercise file and follow the instructions to make the appropriate changes. 135 | 136 | 137 | 138 | There are three things I would like you to note here: 139 | 140 | 1. We named those props above with `render` prefix (`renderHeader`, `renderFooter` etc) but it's just a naming convention, we could have named it anything. It's just nice to prefix it with `render` because someone looking at our code would easily be able tell that we are using this `props` function to render something. 141 | 142 | 2. The "render" props we passed is just any regular javascript function, its just that it's returning some component. We can do really anything here that we can do in a javascript function. When we called these function inside the `Dialog` we could pass some arguments if we like. The only limit here really is your imagination. 143 | 144 | 3. We can pass around components in React as `props`, just like we can pass an object or string or function. At the end of the day, React components are just functions. So take the liberty of passing it around. -------------------------------------------------------------------------------- /src/tutorial/09-Capstone.md: -------------------------------------------------------------------------------- 1 | Now that you have mastered the fundamentals of React, we will build something that brings together the concepts we have learned throughout this tutorial. 2 | 3 | #### Project Description 4 | For this capstone project will build a simple feature that will allow users to search for a publicly traded company using their "stock ticker". Once the user clicks the "Search" button, we will then display the company profile and company financial. 5 | 6 | **Note: The API provided here uses a random data generator to generate company profile and the financial information. 7 | Please do not rely on the accuracy of the data because it is guaranteed to be inaccurate (it's random). 8 | The API expects the ticker to be *3 characters* long. It will give you results if the ticker is 3 characters long and it will throw an error if the ticker is less than or greater than 3 characters. 9 | It also does not check if the ticker is actually a valid ticker. So any random 3 character would work.** 10 | 11 | Below on the left is your solution, and on the right is the final solution. Try a ticker like `XYZ` on the right-hand side and click `Search` to see the expected behavior of this component. 12 | 13 | 14 | 15 | Don't worry about styling, it's already provided for you. 16 | 17 | There's also an API provided to you that has two methods - 18 | - `getCompanyProfile(ticker)` - Returns a `Promise` that resolves to the company profile information for the ticker you passed. It has fields like - description, exchange, industry, sector etc. 19 | - `getCompanyFinancial(ticker)` - Returns a `Promise` that resolves to the financial information for the ticker you passed. The response has fields like - price, beta, volAvg, MktCap etc. 20 | 21 | For this project, the component skeleton is provided inside the 'src/capstone' folder, and as always, the files are heavily commented to guide you with the tasks. Please start with the `Capstone.js` file and you can go to any of the other files next. The final solution is provided in the solution folder (src/capstone/solution), however, I strongly recommend you to try on your own before looking at the solution. If you don't remember some of the concepts, please feel free to go back and refer to the appropriate tutorial page. 22 | 23 | There are four components in this project. Just a brief word on what those components are used for: 24 | 25 | - `Capstone` - Just named Capstone to signify that this component encapsulates our entire project. We import and use below components inside this to build our feature. 26 | - `Search` - This component has search input and the search button. 27 | - `CompanyProfile` - This component renders company profile. It takes a company ticker as `props` and fetches the profile data from the API and renders that data. 28 | - `CompanyFinancial` - This component renders company financial information. It takes a company ticker as `props` and fetches the financial information data from the API and renders it. 29 | - `DataAPI` - This is the API used to fetch the required data. You don't need to change anything on this API. Under the hood it uses a random data generator so please do not believe the data. Like mentioned above, this API will give you results as long as the ticker is 3 characters long and it will throw an error if the ticker is longer than or shorter than 3 characters. It also does not check whether or not the ticker is valid. In real life this would call an actual API that will fetch actual data, but as long as the data structure doesn't change our React code would not change. So don't worry about the API not giving actual data. It's not relevant for us to complete the task at hand. 30 | 31 | #### Disclaimer 32 | How we break down components to build this feature is not set in stone. The component decomposition is more often art than science. We can most definitely build this feature with just one component instead of four, or we could easily break these components into more granular pieces. Please read this [excellent article](https://reactjs.org/docs/thinking-in-react.html) on how to think about components in React. 33 | 34 | In this project, there are certain decisions that have been made to incorporate most of the concepts we have touched on this tutorial. While what we have done definitely works, we could also achieve the same outcomes using different approaches. 35 | 36 | #### Extra Credit 37 | For example, one of the best practices in React is to create "stateless" components (components that do not have `state`) as possible. This doesn't necessarily mean having `state` is bad, it just means that components will be less cluttered. Stateless components receive `props` from their parent components and render something based on the provided `props`. It will make your component really dumb and that should be your goal - **make dumb components**. They are easier to test, easier to reuse and easier to reason about. So in this project, we could've created all of our child components (CompanyProfile, CompanyFinancial, Search) stateless, and [lifted all the state](https://reactjs.org/docs/lifting-state-up.html) to the parent component (Capstone). I will leave this to you as extra credit. You can refactor the code so that the child components are stateless and only the parent component has state. The parent component passes these state down to the child component as `props`. 38 | -------------------------------------------------------------------------------- /src/tutorial/10-Conclusion.md: -------------------------------------------------------------------------------- 1 | Congratulations! Thank you for sticking around 'til the end. I hope you enjoyed the tutorial, and I hope it helped you get off the ground with some React fundamentals. 2 | This tutorial is by no means a comprehensive one. I have tried to cover some grounds on the most basic fundamentals you must know about React. 3 | 4 | If you liked this tutorial, please feel free to share this with others who are trying to learn React. If there is anything you didn't particularly like, or thought was confusing, please feel free to drop me a note or create a PR. Needless to say, your suggestions would help several other folks trying to learn React. 5 | 6 | ## What's next? 7 | 8 | As with anything in software engineering, the best way to learn is by doing. I hope in this tutorial you were able to do the exercises on each section, and also complete the capstone project at the end. The next step is to create your own application and starting fiddling with it. The easiest way to get going is by using [create-react-app](https://github.com/facebook/create-react-app). With this, you don't have to waste a single minute doing any configuration/setup to create a new react application. Everything is configured for you. 9 | 10 | ### Real World example 11 | Also, if you want to explore some real world application written in React, guess what? You have already checked one out. I'm not sure if you noticed, but this tutorial application is written in React. So it's not a bad idea to navigate the code base. Start from `index.js` and explore - see how things are done, play with components, make changes, try to make improvements and see where it leads you. If you think the changes you made locally are useful to the community, please [create a PR](https://github.com/tyroprogrammer/learn-react-app). It will help you and it will help others! 😊 12 | 13 | ## Resources 14 | 15 | **The best place to find further details on any given React related topic is the official [React website](https://reactjs.org/).** It's a highly valuable resource, and you'll find yourself referring to this website long after being comfortable with React. 16 | 17 | There are several resources that I think are extremely helpful - some are linked to during the tutorial and some are not: 18 | 19 | ### Fundamentals 20 | - Go to the official [React documentation](https://reactjs.org/docs/hello-world.html) and go through the "Main concept" sections. There are useful nuggets of information that I may have missed in this tutorial. 21 | - Go through the [official tutorial](https://reactjs.org/tutorial/tutorial.html) on the React website. 22 | - [Some usage and best practices on lifecycle methods](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html) 23 | 24 | ### Advanced 25 | *(You don't need to know any of this to be able to write applications in React.)* 26 | - [Decisions made by React for it's diffing algorithm](https://reactjs.org/docs/reconciliation.html) to figure out what changed in the DOM. 27 | - [Why is the setState function asynchronous?](https://github.com/facebook/react/issues/11527#issuecomment-360199710) 28 | - [How does setState know what to do?](https://overreacted.io/how-does-setstate-know-what-to-do/) 29 | - We briefly glossed over [how React conceptually works](/tutorial/hello-world) but if you are itching to go deeper into it [you can hear straight from the horse's mouth](https://overreacted.io/react-as-a-ui-runtime/). 30 | 31 | 32 | ### Misc 33 | - [ES6 method binding](https://cmichel.io/es6-class-methods-differences/) 34 | 35 | This is not an exhaustive list, and I am sure there are plenty of other good resources on the web. If you find any resources that you think will be useful for beginners please [create a PR](https://github.com/tyroprogrammer/learn-react-app) 😊. 36 | 37 | Happy coding and good luck! -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "noImplicitAny": false, 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "preserve" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------