├── .gitignore ├── finished ├── .gitignore ├── package-lock.json ├── package.json ├── public │ ├── css │ │ └── style.styl │ ├── favicon.ico │ ├── images │ │ ├── ball.svg │ │ ├── glass-1.jpg │ │ ├── glass-10.jpg │ │ ├── glass-13.jpg │ │ ├── glass-14.jpg │ │ ├── glass-2.jpg │ │ ├── glass-3.JPG │ │ ├── glass-4.jpg │ │ ├── glass-5.jpg │ │ ├── glass-6.jpg │ │ ├── glass-7.jpg │ │ ├── glass-8.jpg │ │ └── glass-9.jpg │ └── index.html └── src │ ├── components │ ├── Beer.js │ ├── Header.js │ ├── Loader.js │ ├── Main.js │ ├── Results.js │ ├── Search.js │ └── Single.js │ ├── index.js │ ├── style.css │ └── style.styl ├── gulpfile.js ├── notes-export ├── 01 - Initial React Setup.html ├── 02- Our First React Component.html ├── 03 - Say Hello to JSX.html ├── 04 - Our Second React Component .html ├── 05 - Passing Props.html ├── 06 - Exercise.html ├── 07 - React Router.html ├── 08 - Say Hello To State.html ├── 09 - Caching Beers in LocalStorage.html ├── 10 - Working in Beer-js.html ├── 11 - Adding a Loading State.html ├── 12 - Exercise- Working with a Single-js.html ├── 13 - Working with Forms + Search.html └── 14 - Deploying + Challenges.html ├── notes ├── 01 - Initial React Setup.md ├── 02- Our First React Component.md ├── 03 - Say Hello to JSX.md ├── 04 - Our Second React Component .md ├── 05 - Passing Props.md ├── 06 - Exercise.md ├── 07 - React Router.md ├── 08 - Say Hello To State.md ├── 09 - Caching Beers in LocalStorage.md ├── 10 - Working in Beer-js.md ├── 11 - Adding a Loading State.md ├── 12 - Exercise- Working with a Single-js.md ├── 13 - Working with Forms + Search.md ├── 14 - Deploying + Challenges.md ├── bottom.html └── top.html ├── react-workshop.md ├── readme.md └── starter-files ├── .DS_Store ├── package.json ├── public ├── css │ └── style.styl ├── favicon.ico ├── images │ ├── ball.svg │ ├── glass-1.jpg │ ├── glass-10.jpg │ ├── glass-13.jpg │ ├── glass-14.jpg │ ├── glass-2.jpg │ ├── glass-3.JPG │ ├── glass-4.jpg │ ├── glass-5.jpg │ ├── glass-6.jpg │ ├── glass-7.jpg │ ├── glass-8.jpg │ └── glass-9.jpg └── index.html └── src ├── components └── .gitkeep ├── index.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.zip 3 | beer-me/ 4 | beer/ 5 | *.zip 6 | .DS_Store -------------------------------------------------------------------------------- /finished/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /finished/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "finished", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "1.1.4" 7 | }, 8 | "dependencies": { 9 | "prop-types": "^15.6.0", 10 | "react": "^16.2.0", 11 | "react-dom": "^16.2.0", 12 | "react-router": "^4.2.0", 13 | "react-router-dom": "^4.2.2", 14 | "slugify": "^1.2.9" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /finished/public/css/style.styl: -------------------------------------------------------------------------------- 1 | yellow = #FFC425 2 | black = #0D181C 3 | red = #e13e45 4 | bevan = 'Bevan', cursive; 5 | 6 | 7 | html 8 | font-size 10px 9 | 10 | body 11 | font-family sans-serif 12 | background yellow 13 | background: linear-gradient(to bottom, yellow 50%,darken(yellow,20%) 100%); 14 | background-attachment fixed 15 | 16 | p 17 | font-size 1.8rem 18 | line-height 1.5 19 | 20 | a 21 | color black 22 | 23 | html 24 | box-sizing: border-box; 25 | 26 | *, *:before, *:after 27 | box-sizing: inherit; 28 | 29 | img 30 | max-width 100% 31 | 32 | h1 33 | text-align center 34 | font-family bevan 35 | font-weight normal 36 | transform rotate(-5deg) skew(-20deg) 37 | margin 2rem 0 2rem 0 38 | a 39 | text-decoration none 40 | font-size 10rem 41 | text-shadow 3px 3px 0 alpha(black, 20%) 42 | 43 | .beer 44 | background white 45 | padding 1rem 46 | border-top 1.5rem solid red 47 | flex 1 0 auto 48 | margin 2rem 49 | overflow hidden 50 | width calc(33.3% - 4rem) 51 | box-shadow 0 0 10px alpha(black, 20%) 52 | transition all 0.2s 53 | &:hover 54 | transform scale(1.05) 55 | box-shadow 0 0 20px 2px alpha(black, 30%) 56 | a 57 | text-decoration none 58 | h2 59 | margin 0 60 | font-size 1.5rem 61 | text-align center 62 | margin-bottom 2rem 63 | 64 | .beers 65 | display flex 66 | flex-wrap wrap 67 | max-width 900px 68 | margin 0 auto 69 | 70 | 71 | /* 72 | Search 73 | */ 74 | 75 | .search 76 | text-align center 77 | max-width 500px 78 | margin 2rem auto 79 | form 80 | display flex 81 | & > * 82 | flex 1 83 | background white 84 | border 0 85 | padding 2rem 86 | font-size 2rem 87 | &[type="submit"] 88 | flex-grow 2 89 | background red 90 | color white 91 | text-transform uppercase 92 | &[type="text"] 93 | flex-grow 10 94 | 95 | /* 96 | Loader 97 | */ 98 | 99 | .loader 100 | margin 0 auto 101 | text-align center 102 | font-size 3rem 103 | font-weight 100 104 | 105 | 106 | /* 107 | Single Beer View 108 | */ 109 | 110 | .single-beer 111 | max-width 500px 112 | margin 0 auto 113 | background white 114 | padding 2rem 115 | .label 116 | width 100% 117 | img 118 | max-width 100% 119 | 120 | h3 121 | font-size 2rem 122 | 123 | .glass 124 | img 125 | float left 126 | width 200px 127 | -------------------------------------------------------------------------------- /finished/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/favicon.ico -------------------------------------------------------------------------------- /finished/public/images/ball.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /finished/public/images/glass-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-1.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-10.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-13.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-14.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-2.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-3.JPG -------------------------------------------------------------------------------- /finished/public/images/glass-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-4.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-5.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-6.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-7.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-8.jpg -------------------------------------------------------------------------------- /finished/public/images/glass-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/finished/public/images/glass-9.jpg -------------------------------------------------------------------------------- /finished/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | React App 18 | 19 | 20 |
21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /finished/src/components/Beer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import slug from 'slugify'; 3 | import PropTypes from 'prop-types'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | class Beer extends React.Component { 7 | static propTypes = { 8 | details: PropTypes.object.isRequired 9 | } 10 | 11 | render() { 12 | const { name, labels, id } = this.props.details; 13 | const image = labels ? labels.medium : 'null.jpg'; 14 | 15 | return ( 16 |
17 | 18 |

{name}

19 | {name} 20 | 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default Beer; 27 | -------------------------------------------------------------------------------- /finished/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | 5 | class Header extends React.Component { 6 | static propTypes = { 7 | siteName: PropTypes.string 8 | } 9 | 10 | render() { 11 | return ( 12 |

13 | {this.props.siteName} 14 |

15 | ); 16 | } 17 | }; 18 | 19 | export default Header; 20 | -------------------------------------------------------------------------------- /finished/src/components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Loader = (props) => ( 5 |
6 | Loading... 7 |

{props.message}

8 |
9 | ); 10 | 11 | Loader.propTypes = { 12 | message: PropTypes.string 13 | }; 14 | 15 | export default Loader; 16 | -------------------------------------------------------------------------------- /finished/src/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Results from "./Results"; 3 | import Search from "./Search"; 4 | import Header from "./Header"; 5 | 6 | class Main extends React.Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | beers: [], 11 | loading: true 12 | }; 13 | } 14 | 15 | componentDidMount() { 16 | console.log(`mounting`); 17 | console.log(this); 18 | const params = this.props.match.params || {}; 19 | const searchTerm = params.searchTerm || undefined; 20 | this.loadBeers(searchTerm); 21 | } 22 | 23 | // componentWillReceiveProps(nextProps) { 24 | // console.log("Will receive props!"); 25 | // console.log(nextProps); 26 | // this.loadBeers(nextProps.match.params.searchTerm); 27 | // } 28 | componentDidUpdate(prevProps) { 29 | console.log('did update'); 30 | const currentSearchTerm = this.props.match.params.searchTerm; 31 | const oldSearchTerm = prevProps.match.params.searchTerm; 32 | if (currentSearchTerm !== oldSearchTerm) { 33 | this.loadBeers(currentSearchTerm); 34 | } 35 | } 36 | 37 | loadBeers = (searchTerm = "hops") => { 38 | this.setState({ loading: true }); 39 | 40 | // Check for beers in local storage 41 | const localStorageBeers = localStorage.getItem(`search-${searchTerm}`); 42 | 43 | if (localStorageBeers) { 44 | const localBeers = JSON.parse(localStorageBeers); 45 | this.setState({ beers: localBeers, loading: false }); 46 | return; // stop before fetch happens! 47 | } 48 | 49 | fetch(`http://api.react.beer/v2/search?q=${searchTerm}&type=beer`) 50 | .then(data => data.json()) 51 | .then(data => { 52 | // filter for beers with images 53 | const beers = data.data || []; 54 | const filteredBeers = beers.filter(beer => !!beer.labels); 55 | this.setState({ beers: filteredBeers, loading: false }); 56 | // save to local storage in case we search for this again 57 | localStorage.setItem( 58 | `search-${searchTerm}`, 59 | JSON.stringify(this.state.beers) 60 | ); 61 | }) 62 | .catch(err => console.error(err)); 63 | }; 64 | 65 | render() { 66 | return ( 67 |
68 |
69 | 70 | 71 |
72 | ); 73 | } 74 | } 75 | 76 | export default Main; 77 | -------------------------------------------------------------------------------- /finished/src/components/Results.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Beer from './Beer'; 3 | import Loader from './Loader'; 4 | import PropTypes from 'prop-types'; 5 | 6 | class Results extends React.Component { 7 | static propTypes = { 8 | loading: PropTypes.bool.isRequired, 9 | beers: PropTypes.array.isRequired 10 | } 11 | 12 | render() { 13 | if (this.props.loading) { 14 | return ; 15 | } 16 | 17 | return ( 18 |
19 |
20 | {this.props.beers.map((details, i) => )} 21 |
22 |
23 | ); 24 | } 25 | }; 26 | 27 | export default Results; 28 | -------------------------------------------------------------------------------- /finished/src/components/Search.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Search extends React.Component { 5 | 6 | static contextTypes = { 7 | router: PropTypes.object.isRequired 8 | } 9 | 10 | searchRef = React.createRef(); 11 | 12 | handleSubmit = (e) => { 13 | e.preventDefault(); 14 | const searchTerm = this.searchRef.current.value; 15 | this.context.router.history.push(`/search/${searchTerm}`); 16 | } 17 | 18 | render() { 19 | return ( 20 |
21 |
22 | 23 | 24 |
25 |
26 | ); 27 | } 28 | }; 29 | 30 | 31 | export default Search; 32 | -------------------------------------------------------------------------------- /finished/src/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Loader from "./Loader"; 3 | import Header from "./Header"; 4 | import PropTypes from "prop-types"; 5 | 6 | class Single extends React.Component { 7 | constructor() { 8 | super(); 9 | this.state = { beer: {}, loading: true }; 10 | } 11 | 12 | static propTypes = { 13 | params: PropTypes.object 14 | }; 15 | 16 | componentDidMount() { 17 | console.log(`searching for ${this.props.match.params.beerId}`); 18 | this.loadBeer(this.props.match.params.beerId); 19 | } 20 | 21 | loadBeer = beerId => { 22 | console.log(`Loading beer ${beerId}`); 23 | this.setState({ loading: true }); 24 | fetch(`http://api.react.beer/v2/beer/${beerId}`) 25 | .then(data => data.json()) 26 | .then(res => { 27 | this.setState({ beer: res.data, loading: false }); 28 | }); 29 | }; 30 | 31 | renderGlass = beer => { 32 | if (!beer.glass) return; 33 | return ( 34 |
35 | {beer.name} 36 |

{beer.glass.name} Glass

37 |
38 | ); 39 | }; 40 | 41 | renderAbv = beer => { 42 | if (!beer.abv) return; 43 | return
ABV: {beer.abv}%
; 44 | }; 45 | 46 | render() { 47 | if (this.state.loading) { 48 | return ; 49 | } 50 | 51 | const { beer } = this.state; 52 | 53 | return ( 54 |
55 |
56 |
57 |
58 |

{beer.name}

59 |

{beer.description}

60 |
61 | 62 | {beer.name} 63 | 64 |
65 | {this.renderGlass(beer)} 66 | {this.renderAbv(beer)} 67 |
68 | 69 |
70 |

More Info on {beer.style.name}

71 |

{beer.style.description}

72 |
73 |
74 |
75 | ); 76 | } 77 | } 78 | 79 | export default Single; 80 | -------------------------------------------------------------------------------- /finished/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import Main from './components/Main'; 4 | import Single from './components/Single'; 5 | 6 | import { BrowserRouter, Route } from 'react-router-dom'; 7 | 8 | /* Import CSS */ 9 | import './style.css'; 10 | 11 | const Root = function() { 12 | return ( 13 | 14 |
15 | 16 | 17 | 18 |
19 |
20 | ); 21 | }; 22 | 23 | render(, document.querySelector('#root')); 24 | -------------------------------------------------------------------------------- /finished/src/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 10px; 3 | } 4 | 5 | body { 6 | font-family: sans-serif; 7 | background: #ffc425; 8 | background: linear-gradient(to bottom, #ffc425 50%, #eaaa00 100%); 9 | background-attachment: fixed; 10 | } 11 | 12 | p { 13 | font-size: 1.8rem; 14 | line-height: 1.5; 15 | } 16 | 17 | a { 18 | color: #0d181c; 19 | } 20 | 21 | html { 22 | box-sizing: border-box; 23 | } 24 | 25 | *, 26 | *:before, 27 | *:after { 28 | box-sizing: inherit; 29 | } 30 | 31 | img { 32 | max-width: 100%; 33 | } 34 | 35 | h1 { 36 | text-align: center; 37 | font-family: 'Bevan', cursive; 38 | font-weight: normal; 39 | transform: rotate(-5deg) skew(-20deg); 40 | margin: 2rem 0 2rem 0; 41 | } 42 | 43 | h1 a { 44 | text-decoration: none; 45 | font-size: 10rem; 46 | text-shadow: 3px 3px 0 rgba(13,24,28,0.2); 47 | } 48 | 49 | .beer { 50 | background: #fff; 51 | padding: 1rem; 52 | border-top: 1.5rem solid #e13e45; 53 | flex: 1 0 auto; 54 | margin: 2rem; 55 | overflow: hidden; 56 | width: calc(33.3% - 4rem); 57 | box-shadow: 0 0 10px rgba(13,24,28,0.2); 58 | transition: all 0.2s; 59 | } 60 | 61 | .beer:hover { 62 | transform: scale(1.05); 63 | box-shadow: 0 0 20px 2px rgba(13,24,28,0.3); 64 | } 65 | 66 | .beer a { 67 | text-decoration: none; 68 | } 69 | 70 | .beer h2 { 71 | margin: 0; 72 | font-size: 1.5rem; 73 | text-align: center; 74 | margin-bottom: 2rem; 75 | } 76 | 77 | .beers { 78 | display: flex; 79 | flex-wrap: wrap; 80 | max-width: 900px; 81 | margin: 0 auto; 82 | } 83 | /* 84 | Search 85 | */ 86 | .search { 87 | text-align: center; 88 | max-width: 500px; 89 | margin: 2rem auto; 90 | } 91 | 92 | .search form { 93 | display: flex; 94 | } 95 | 96 | .search form > * { 97 | flex: 1; 98 | background: #fff; 99 | border: 0; 100 | padding: 2rem; 101 | font-size: 2rem; 102 | } 103 | 104 | .search form > *[type="submit"] { 105 | flex-grow: 2; 106 | background: #e13e45; 107 | color: #fff; 108 | text-transform: uppercase; 109 | } 110 | 111 | .search form > *[type="text"] { 112 | flex-grow: 10; 113 | } 114 | /* 115 | Loader 116 | */ 117 | .loader { 118 | margin: 0 auto; 119 | text-align: center; 120 | font-size: 3rem; 121 | font-weight: 100; 122 | } 123 | /* 124 | Single Beer View 125 | */ 126 | .single-beer { 127 | max-width: 500px; 128 | margin: 0 auto; 129 | background: #fff; 130 | padding: 2rem; 131 | } 132 | 133 | .single-beer .label { 134 | width: 100%; 135 | } 136 | 137 | .single-beer img { 138 | max-width: 100%; 139 | } 140 | 141 | .single-beer h3 { 142 | font-size: 2rem; 143 | } 144 | 145 | .glass img { 146 | float: left; 147 | width: 200px; 148 | } 149 | -------------------------------------------------------------------------------- /finished/src/style.styl: -------------------------------------------------------------------------------- 1 | yellow = #FFC425 2 | black = #0D181C 3 | red = #e13e45 4 | bevan = 'Bevan', cursive; 5 | 6 | 7 | html 8 | font-size 10px 9 | 10 | body 11 | font-family sans-serif 12 | background yellow 13 | background: linear-gradient(to bottom, yellow 50%,darken(yellow,20%) 100%); 14 | background-attachment fixed 15 | 16 | p 17 | font-size 1.8rem 18 | line-height 1.5 19 | 20 | a 21 | color black 22 | 23 | html 24 | box-sizing: border-box; 25 | 26 | *, *:before, *:after 27 | box-sizing: inherit; 28 | 29 | img 30 | max-width 100% 31 | 32 | h1 33 | text-align center 34 | font-family bevan 35 | font-weight normal 36 | transform rotate(-5deg) skew(-20deg) 37 | margin 2rem 0 2rem 0 38 | a 39 | text-decoration none 40 | font-size 10rem 41 | text-shadow 3px 3px 0 alpha(black, 20%) 42 | 43 | .beer 44 | background white 45 | padding 1rem 46 | border-top 1.5rem solid red 47 | flex 1 0 auto 48 | margin 2rem 49 | overflow hidden 50 | width calc(33.3% - 4rem) 51 | box-shadow 0 0 10px alpha(black, 20%) 52 | transition all 0.2s 53 | &:hover 54 | transform scale(1.05) 55 | box-shadow 0 0 20px 2px alpha(black, 30%) 56 | a 57 | text-decoration none 58 | h2 59 | margin 0 60 | font-size 1.5rem 61 | text-align center 62 | margin-bottom 2rem 63 | 64 | .beers 65 | display flex 66 | flex-wrap wrap 67 | max-width 900px 68 | margin 0 auto 69 | 70 | 71 | /* 72 | Search 73 | */ 74 | 75 | .search 76 | text-align center 77 | max-width 500px 78 | margin 2rem auto 79 | form 80 | display flex 81 | & > * 82 | flex 1 83 | background white 84 | border 0 85 | padding 2rem 86 | font-size 2rem 87 | &[type="submit"] 88 | flex-grow 2 89 | background red 90 | color white 91 | text-transform uppercase 92 | &[type="text"] 93 | flex-grow 10 94 | 95 | /* 96 | Loader 97 | */ 98 | 99 | .loader 100 | margin 0 auto 101 | text-align center 102 | font-size 3rem 103 | font-weight 100 104 | 105 | 106 | /* 107 | Single Beer View 108 | */ 109 | 110 | .single-beer 111 | max-width 500px 112 | margin 0 auto 113 | background white 114 | padding 2rem 115 | .label 116 | width 100% 117 | img 118 | max-width 100% 119 | 120 | h3 121 | font-size 2rem 122 | 123 | .glass 124 | img 125 | float left 126 | width 200px 127 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var markdown = require('gulp-markdown'); 3 | var zip = require('gulp-zip'); 4 | 5 | var fs = require('fs'); 6 | var notesTop = fs.readFileSync('notes/top.html'); 7 | var notesBottom = fs.readFileSync('notes/bottom.html'); 8 | var insert = require('gulp-insert'); 9 | 10 | gulp.task('notes',function() { 11 | return gulp.src('notes/**/*.md') 12 | .pipe(markdown({ 13 | gfm: true 14 | })) 15 | .pipe(insert.prepend(notesTop)) 16 | .pipe(insert.append(notesBottom)) 17 | .pipe(gulp.dest('notes-export')) 18 | .pipe(zip('gulp-notes.zip')) 19 | .pipe(gulp.dest('.')); 20 | }); 21 | 22 | // gulp.task('exercises', function() { 23 | // gulp.src('./CODE/*') 24 | // .pipe(zip('gulp-exercises.zip')) 25 | // .pipe(gulp.dest('.')) 26 | // }); 27 | 28 | gulp.task('default',['notes']); 29 | -------------------------------------------------------------------------------- /notes/01 - Initial React Setup.md: -------------------------------------------------------------------------------- 1 | ## Env Setup 2 | 3 | In order to write React code, we need to have a little tooling in place. 4 | 5 | React is written in ES6 modules. Rather than use a bunch of Script tags and dump them into our code, we use ES6 modules to import and export our components. Much more on this as we go along. 6 | 7 | In order to work with modules, we need a few things: 8 | 9 | 1. **npm** — Used for installing our dependencies - like React! 10 | 2. **node.js** — the tooling we use runs on Node.js 11 | 3. **Webpack** — one of the most popular bundlers for JavaScript modules. We will be using Webpack behind the scenes with a command line tool called `create-react-app`. 12 | 4. **Terminal** — the bundling of our code happens in the Terminal so you'll need either the OSX terminal app or the Windows PowerShell installed 13 | 14 | So, let's head on over to our `beer-me` directory. The `package.json` file already includes the dependencies that we need, so we just need to run `npm install` to fetch and install them. 15 | 16 | Our webpack process is setup for using `index.js` as our entry point. Open it up and type: 17 | 18 | ```js 19 | alert('it works!'); 20 | ``` 21 | 22 | ## Running our App 23 | 24 | To start the app, simply run `npm start`. Webpack (via `create-react-app`) will build the site and open it up in your browser. Since all we have written so far is just a alert, you should see it when the browser opens. 25 | 26 | ![](http://wes.io/fc6Y/content) 27 | 28 | ## Hello World in React 29 | 30 | Let's get a paragraph tag displayed on the page. 31 | 32 | First we need to import React and something called react-dom which will allow us to display HTML. 33 | 34 | Since both `react` and `react-dom` are both npm packages we we already installed, we can simply require the entire React lib, and then use curly brackets to import just the one `render` method from `react-dom`: 35 | 36 | ```js 37 | import React from 'react'; 38 | import { render } from 'react-dom'; 39 | ``` 40 | 41 | Next, we need to render out something. React needs an entry point where it can "mount" itself. If you look at `index.html` you'll see that we have an empty div with the id of `root`. 42 | 43 | ``` 44 | render(

Hello World

, document.querySelector('#root')); 45 | ``` 46 | 47 | Let's break this down: 48 | 49 | 1. `render()` is the method we just imported 50 | 2.

Hello World

is the element we wish to render — this is really something called JSX. More on this in a bit! It's important to note thay we don't have to put it in quotes. 51 | 3. document.querySelector('#root') finds the entry point in the DOM 52 | 53 | Save and refresh your page, you should see the very basic of getting up and running with React. If you open your dev tools now you should see the React tab which you can now inspect: 54 | 55 | ![](http://wes.io/fcCv/content) 56 | 57 | ## CSS 58 | 59 | We have two options for using CSS with a React app. 60 | 61 | 1. You can load in a `style.css` file all on your own into your index.html and not have React or Webpack play any part of it. 62 | 2. You can let Webpack handle importing the CSS for you. The advantages of this is that you _could_ write CSS the directly relates to each component and only have it imported as you need it. 63 | 64 | This is a very divisive topic in the web development community so I'll let you make your own decisions here. For us, I'm going to import all our CSS at the top level app. 65 | 66 | In your `index.js`, place this just above your render: 67 | 68 | ```js 69 | /* Import CSS */ 70 | import css from './style.css'; 71 | ``` 72 | 73 | I'm using plain cSS here - but you could use Sass, Less, Post CSS or regular CSS if you'd like. 74 | 75 | However, since this all happens inside the WebPack file with something called _loaders_, we would need to _eject_ from `create-react-app`. More on this later. 76 | -------------------------------------------------------------------------------- /notes/02- Our First React Component.md: -------------------------------------------------------------------------------- 1 | ## Our First React Component 2 | 3 | Now - we can't just be writing all of our HTML inside of the render method — we need to break things up into their own components. Let's write our first React component. 4 | 5 | In React, everything is a component. You can think of our application as many parts that we piece together to make an application as a whole. 6 | 7 | Let's look at the front page of our app. What are the components we are going to make? 8 | 9 | ![](http://wes.io/fcCO/content) 10 | 11 | Let's start with a `Header.js` component. In react, since components are sort of like classes that can be reused over and over, it's a standard practice to name your files and components with a capital. 12 | 13 | So in a folder named `components`, create `components/Header.js`. 14 | 15 | Now there are a few things we need to create a component: 16 | 17 | 1. React itself 18 | 19 | ```js 20 | import React from 'react'; 21 | ``` 22 | 23 | Notice that even though we have imported React into `index.js`, we also import it in here. Modules are not global and you _must_ re-import the react library into every point that you need it. 24 | 25 | Then we create our component and store it in a variable. We extend `React.Component` to create the react class here. 26 | 27 | ## Quick Aside 28 | 29 | We are extending React.Component here with ES6 classes. Since ES6 classes currently fall short of a few much needed properties, we will be using some features that are "soon-to-come" to JavaScript to fill in those holes. 30 | 31 | The major shortcoming of ES6 classes and React is that the keyword `this` is not bound to the component instance. To get around this, we will be using [class properties](https://babeljs.io/docs/plugins/transform-class-properties/) which are not yet in the language but will be compiled down for us. I'll be sure to touch on this as we learn, but this is a heads up that you may see some JS syntax that you have never encountered before. 32 | 33 | ## Back to it 34 | 35 | Every React component will have multiple methods that live inside it, but the one method that we absolutely need is the `render()` method. This is a pre-defined method that React looks for when it displays the content on our page. 36 | 37 | Let's type this one together to get the hang of the syntax and answer the following questions along the way: 38 | 39 | 1. Why do we use `const`? 40 | 2. What is this `render() {}` business? should it not be `render: function() {}`? 41 | 3. Why is there (parens) around everything? 42 | 43 | ```js 44 | class Header extends React.Component { 45 | render() { 46 | return ( 47 |

Beer Me!

48 | ) 49 | } 50 | }; 51 | ``` 52 | 53 | Finally, since we will need to import this component into other components, we need to **export it** with `export default Header`. 54 | 55 | Our final code looks like this: 56 | 57 | ```js 58 | import React from 'react'; 59 | 60 | class Header extends React.Component { 61 | render() { 62 | return ( 63 |

Beer Me!

64 | ) 65 | } 66 | }; 67 | 68 | export default Header; 69 | 70 | ``` 71 | 72 | 73 | ### Displaying `
` 74 | 75 | We have now made a Header component. Swap out the `

Hello World

` with our new Header component in `index.js`. We can use the "tag" `
` to reference our component: 76 | 77 | ```js 78 | render(
, document.querySelector('#root')); 79 | ``` 80 | 81 | But you'll see that we get this error: 82 | 83 | ![](http://wes.io/fcAo/content) 84 | 85 | That is because we haven't yet **imported** our component into index.js! 86 | 87 | ```js 88 | import Header from './components/Header'; 89 | ``` 90 | 91 | Since the component is not part of our `node_modules` directory, we do a relative reference with `./`. Refresh your page and you should now see the title: 92 | 93 | 94 | ![](http://wes.io/fc1Y/content) 95 | -------------------------------------------------------------------------------- /notes/03 - Say Hello to JSX.md: -------------------------------------------------------------------------------- 1 | ## Say Hello to JSX 2 | 3 | JSX is the templating language we write in order to create HTML or DOM fragments. It's not technically JavaScript, but Babel and Webpack are able to convert it from the nice syntax that we know and understand into the JavaScript equivalent of `React.createElelement`. 4 | 5 | Writing JSX is very similar to JavaScript except for a few items. 6 | 7 | ### Self Closing Elements 8 | 9 | Elements **must** close. This isn't an issue for most tags: 10 | 11 | ```html 12 |

Beer Me!

13 | ``` 14 | 15 | But self-closing elements like `input` and `img` need to have the closing `/` in them: 16 | 17 | ```html 18 | 19 | 20 | 21 | 22 | 23 | ``` 24 | 25 | ### Must return only one element 26 | 27 | React expects you to only return one element. You can have as many child elements as you wish, but only one top level element. 28 | 29 | So, if I needed to return the following HTML. You'll get an error saying `Adjacent JSX elements must be wrapped in an enclosing tag (8:6)`. 30 | 31 | ```html 32 |

Beer Name

33 |

Beer Description goes here

34 | ``` 35 | 36 | An easy fix is to wrap it in a div: 37 | 38 | ```html 39 |
40 |

Beer Name

41 |

Beer Description goes here

42 |
43 | ``` 44 | 45 | One major downside to this is that it interferes with CSS where the parent-child relationship is important. Extra divs can really goof up your Flexbox or CSS Grid code. 46 | 47 | As of React 16.2, we are now able to use something called Fragments which won't render out a containing div: 48 | 49 | ```js 50 | <> 51 |

Beer Name

52 |

Beer Description goes here

53 | 54 | ``` 55 | 56 | ### Comments 57 | 58 | Comments are a little weird in JSX, since we are _technically_ writing JavaScript, we need to use JavaScript comments, but inside JSX curly braces. 59 | 60 | So a comment in JSX looks like 61 | 62 | ```html 63 |
64 | {/* Comment here */ } 65 |

Beer Name

66 |

Beer Description goes here

67 |
68 | ``` 69 | 70 | Another gotcha is that you may not have a comment at the top level, for the same reason we can only return one element: 71 | 72 | ```html 73 | {/* Comment cannot go here */ } 74 |
75 |

Beer Name

76 |

Beer Description goes here

77 |
78 | ``` 79 | 80 | ### className instead of class 81 | 82 | Because `class` is a reserved word in JavaScript, you must use className instead. 83 | 84 | ```html 85 |

Hello

86 | ``` 87 | 88 | is now 89 | 90 | ```html 91 |

Hello

92 | ``` 93 | 94 | If you use Emmet, your expansions will be automatically updated for you. 95 | -------------------------------------------------------------------------------- /notes/04 - Our Second React Component .md: -------------------------------------------------------------------------------- 1 | ## Our Second React Component 2 | 3 | Before we can display that component, we need a wrapper component that will hold all everything. 4 | 5 | At the end of the day, our JSX is going to look similar to this: 6 | 7 | ```html 8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | ``` 20 | 21 | Create a component called `Main.js` in your components directory. 22 | 23 | In that file, we will need to do the same three things we do in every component: 24 | 25 | 1. Import React 26 | 2. Create the component with `React.createClass()` 27 | 3. Export your Component 28 | 29 | ``` 30 | import React from 'react'; 31 | import Header from './Header'; 32 | 33 | class Main extends React.Component { 34 | render() { 35 | return ( 36 |
37 |
38 |
39 | ) 40 | } 41 | }; 42 | 43 | export default Main; 44 | ``` 45 | 46 | Now in `index.js` import Main: `import Main from './components/Main';` and switch out `
` with `
`. 47 | 48 | See where we are going with this? We indirectly display the `
` because it's part of `
`. Take a look at your React dev tools now. 49 | N 50 | ![](http://wes.io/fcxb/content) 51 | -------------------------------------------------------------------------------- /notes/05 - Passing Props.md: -------------------------------------------------------------------------------- 1 | ## Passing Props 2 | 3 | In React, there are two main ways in which you can provide data to a component — **state** and **props**. We're going to learn about props and later on, state. 4 | 5 | Our Header.js file is hard coded with "Beer Me!" — what if we had that data at the Main component and want to pass it down to Header? 6 | 7 | **To pass down data to a child component, we use props.** 8 | 9 | Props look just like attributes. Let's change our `
` to take a prop called `siteName`. 10 | 11 | To pass the prop, simply pass it like an attribute: 12 | 13 | ```html 14 |
15 | ``` 16 | 17 | You can pass any type down - strings, numbers, methods. Anything other than a string will need curly brackets: 18 | 19 | Pass down a property: 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | Use a function: 26 | 27 | ```html 28 | 29 | ``` 30 | 31 | Pass a function 32 | 33 | ```html 34 | 35 | ``` 36 | 37 | We will go more in depth on this soon! 38 | 39 | ### Using Props 40 | 41 | Once props are passed _to_ a component, we can use them access them inside that component. If you inspect your `
` component with react dev tools you should now see that the there is an object inside the component called `props`: 42 | 43 | ![](http://wes.io/fcPQ/content) 44 | 45 | We can access the props inside our render component by using `this.props.siteName`. 46 | 47 | ```html 48 |

Beer Me!

49 | ``` 50 | 51 | turns into: 52 | 53 | ```html 54 |

{this.props.siteName}

55 | ``` 56 | 57 | A few things to note: 58 | 59 | 1. Notice how we use `{curly braces}` to access variables inside our JSX? 60 | 2. React automatically binds all methods inside the component to the Component itself. What does that mean? In any method (render for this case) `this` refers to `
`. 61 | 62 | 63 | ## PropTypes 64 | 65 | When using components, you don't always know which props you should be using. If a co-worker created the component, how do you know which props it requires? 66 | 67 | PropTypes will check that the necessary data, and the type of data being passed to the component is done correctly. 68 | 69 | So, anytime you use props, your component must have propTypes. 70 | 71 | Let's take `
` in `Header.js` for example. What props do we use there? 72 | 73 | ``` 74 | class Header extends React.Component { 75 | render() { 76 | return ( 77 |

{this.props.siteName}

78 | ); 79 | } 80 | }; 81 | ``` 82 | 83 | `siteName`! 84 | 85 | And what type is it? A String! 86 | 87 | First we need to import `PropTypes` from the `prop-types` package. 88 | 89 | ``` 90 | import PropTypes from 'prop-types' 91 | // you can also import just the items you need: 92 | import { string, number } from 'prop-types'; 93 | // you may also see an older style React.PropTypes. This is deprecated 94 | import { PropTypes } from 'react'; 95 | ``` 96 | 97 | 98 | So, we can add propTypes to our component: 99 | 100 | ``` 101 | class Header extends React.Component { 102 | 103 | static propTypes = { 104 | siteName: PropTypes.string.isRequired 105 | } 106 | 107 | render() { 108 | return ( 109 |

{this.props.siteName}

110 | ); 111 | } 112 | }; 113 | ``` 114 | 115 | A few things: 116 | 1. We use `static propType =` to set the property on the component. 117 | 2. there is no `,` after the object or method. This is part of ES6 Classes and differs from an object where you must put a `,` 118 | 119 | This will now error in your console if you: 120 | 121 | 1) Forget to pass something 122 | 2) Pass a number, boolean, function or object. 123 | 124 | More info on all the available propTypes here: 125 | 126 | 127 | -------------------------------------------------------------------------------- /notes/06 - Exercise.md: -------------------------------------------------------------------------------- 1 | ## Exercise: A few More Components 2 | 3 | We need to create a few more components and scaffold out our application. 4 | 5 | 1. A **Loader.js** component which will take a prop of "message" and display it. Your HTML should look like this: 6 | 7 | ```html 8 |
9 | 10 |

Your Message from props goes here

11 |
12 | ``` 13 | 14 | 2. A **Beer.js** component, for now just render out `
Beer will go here
` 15 | 3. A **Results.js** component, for how just render out `
Results go here
` 16 | 4. A **Search.js** component, for now just render out `
Search goes here
` 17 | 4. A **Single.js** component, for now just render out `
A single beer goes here
` 18 | -------------------------------------------------------------------------------- /notes/07 - React Router.md: -------------------------------------------------------------------------------- 1 | 2 | ## Routing with React Router 3 | 4 | Our application is going to have three "routes", or urls that a user can hit. 5 | 6 | 1. `localhost:3000/` for the home page and default beers 7 | 1. `localhost:3000/search/angry` the search results for a specific keyword. In this case `angry` 8 | 1. `http://localhost:3000/beer/OUqh1N/Texas-Craft-Brewers-Guild` the single view page for a specific beer - we pass along the beer ID and the beer name in the url. 9 | 10 | React is just a view library so it doesn't have routing built in like other frameworks might have. That said, the community has primarily standardized on using [React Router](https://github.com/ReactTraining/react-router) 11 | 12 | React router is just a component in itself - it watches for history changes and will render out the correct component for you. 13 | 14 | On every page we want to render either the `
` component on the Home page and search page or the `` on the single beer page. 15 | 16 | 17 | ### Step 1: Import react-router 18 | 19 | There are a number of things we need from the `react-router-dom` package. In `index.js`, import the following: 20 | 21 | ```js 22 | import { BrowserRouter, Route } from 'react-router-dom'; 23 | ``` 24 | 25 | ### Step 2: Update our index.js Render 26 | 27 | In `index.js` we are going to change our out render function to take the router instead of the `
` component. 28 | 29 | Currently it looks like this: 30 | 31 | ```html 32 | render(
, document.querySelector('#root')); 33 | ``` 34 | 35 | And we will update it to look like this. Let's code it together and talk though each of the parts. 36 | 37 | ``` 38 | const Root = function() { 39 | return ( 40 | 41 |
42 | 43 | 44 | 45 |
46 |
47 | ); 48 | }; 49 | 50 | render(, document.querySelector('#root')); 51 | ``` 52 | 53 | **Finally,** you'll notice we are referencing the `Results` and `Single` components here. Make sure you import them at the top of your document alongside the already imported `Main`: 54 | 55 | Our entire `index.js` now looks like: 56 | 57 | ```html 58 | import React from 'react'; 59 | import { render } from 'react-dom'; 60 | import Main from './components/Main'; 61 | import Single from './components/Single'; 62 | 63 | import { BrowserRouter, Route } from 'react-router-dom'; 64 | 65 | /* Import CSS */ 66 | import './style.css'; 67 | 68 | const Root = function() { 69 | return ( 70 | 71 |
72 | 73 | 74 | 75 |
76 |
77 | ); 78 | }; 79 | 80 | render(, document.querySelector('#root')); 81 | ``` 82 | 83 | ### Step 3: Try it out 84 | 85 | You should be able to go to any of the matching routes: 86 | 87 | * 88 | * 89 | * 90 | -------------------------------------------------------------------------------- /notes/08 - Say Hello To State.md: -------------------------------------------------------------------------------- 1 | ## Say Hello To State 2 | 3 | Up until this point, React may just seem like a glorified templating engine. This is the section where you may have an "Ah-Ha!" moment and understand why React shines. 4 | 5 | React components have a concept called **state** where we hold data related to our application. State is the the _source of truth_ for our components and whenever _anything_ in state changes, the application's DOM will update itself. 6 | 7 | If you are coming from a jQuery background, this is the real power of React. Rather than manually update the DOM ourselves when something happens, we simply just update this big object called **state** and react will figure out which parts of the DOM need to be updated! 8 | 9 | We will use state to hold a list of beers, but before we even do that, let's do a simple example together. 10 | 11 | We're going to make a simple beer counter - a button, that when clicked, will increment the number of beers you need to buy for a party. Essentially just a button that will increment every time you click it. Very simple, but will illustrate a few core concepts that we will use for this application. 12 | 13 | ### Step 1: Set Initial State 14 | 15 | When a component mounts, we need what is called the initial state — what the data will start with. We can update this state or load in external data in just a second, but for now we need to declare the very first state. 16 | 17 | This is done by setting state in the classes constructor. The class constructor is a method that will run as the class is created. We can set our state in here. 18 | 19 | Let's start with 10 beer: 20 | 21 | ```js 22 | constructor() { 23 | super(); 24 | this.state = { 25 | numBeers : 10 26 | } 27 | } 28 | ``` 29 | 30 | ### Step 2: Display The State in JSX 31 | 32 | Right below the `{this.state.numBeers} 🍺 36 | ``` 37 | 38 | See how we used `{this.state.numBeers}`? Refresh your page and you should see 39 | 40 | ![](http://wes.io/fdFt/content) 41 | 42 | ### Step 3: Create a method to update that number 43 | 44 | Along with the built in methods like `render`, we can create our own methods that will handle the updating of state. We will create a method on Main called `incrementBeers`. Note how we set the instance property with the new syntax of `incrementBeers = () => {`. This allows us to reference `this` inside the method and have `this` be equal to that instance of the react component. A regular method will not be bound to the React component unless you explicitly bind it in the constructor. 45 | 46 | ```js 47 | incrementBeers = () => { 48 | // create a new upated state variable 49 | const beerAmount = this.state.numBeers + 1; 50 | // set state to that amount 51 | this.setState({ numBeers: beerAmount }); 52 | }; 53 | ``` 54 | 55 | Now whenever that method is run, state will be incremented by one. Notice how we are explicitly setting state here and not just `this.state.numBeers++`. 56 | 57 | ### Step 4: Hook up that method to the button 58 | 59 | Finally, the way we handle clicks and other events in React is through the `onClick` event handler. React has re-implemented all of JavaScripts inline events so they may seem familiar. 60 | 61 | ```html 62 | 63 | ``` 64 | 65 | ### Step 5: Click away 66 | 67 | Go ahead and click that button a few times. When you click it, it triggers `incrementBeers`, which will update state. When state is updated, React knows we are referencing `{this.state.numBeers}` and will update our button for us. 68 | 69 | That there is the key to understanding how react works. You update your data, not your DOM/HTML. 70 | 71 | 72 | 73 | ## Updating State with Ajax 74 | 75 | Now it's time to pull in a list of beers from our Beer API. 76 | 77 | ### Step 1: Initial State 78 | 79 | We start by applying an empty array of beers to the initial state. Our `constructor` should now look like this: 80 | 81 | ```js 82 | constructor() { 83 | super(); 84 | this.state = { 85 | numBeers : 10, 86 | beers: [] 87 | } 88 | } 89 | ``` 90 | 91 | Note: We can also specify state directly on our component now. This is exactly the same as the above with a bit of a cleaner syntax: 92 | 93 | ```js 94 | class Wes extends React.Components { 95 | state = { 96 | name: "Wes" 97 | }; 98 | 99 | render() { 100 | return

Hello my name is {this.state.name}

; 101 | } 102 | } 103 | ``` 104 | 105 | ### Step 2: Fetch JSON 106 | 107 | The endpoint we are going to hit is 108 | 109 | Where the `q` param will either be `hops` by default, or something inputted by the user. Since the listing of beers will be attached to the Main component, we can create a new method called `loadBeers()` on that component. 110 | 111 | We will be using the new browser api `fetch()` to go and grab this data. It works very similar to jquery's `$.getJSON()`. 112 | 113 | Again, try not to copy/paste but rather let's work through the following code ourselves: 114 | 115 | ```js 116 | loadBeers = (searchTerm = "hops") => { 117 | fetch(`http://api.react.beer/v2/search?q=${searchTerm}&type=beer`) 118 | .then(data => data.json()) 119 | .then(beers => { 120 | console.log(beers); 121 | // filter for beers with images 122 | const filteredBeers = beers.data.filter(beer => !!beer.labels); 123 | this.setState({ beers: filteredBeers }); 124 | }) 125 | .catch(err => console.error(err)); 126 | }; 127 | ``` 128 | 129 | ### Step 3: Trigger on load 130 | 131 | We need to trigger this `fetchBeers()` method on load. For this, we will use another one of React's lifecycle methods called `componentDidMount()` : 132 | 133 | ```js 134 | componentDidMount() { 135 | this.loadBeers(); 136 | } 137 | ``` 138 | 139 | Refresh the page and the Ajax request should trigger. If all went well you should see a list of beers in your component's state: 140 | 141 | ![](http://wes.io/fdR6/content) 142 | 143 | ## Displaying State with JSX 144 | 145 | Now we have data in our state how do we display it? 146 | 147 | We would like to display the results in `Results.js`, but we fetched the beer in `Main.js`. 148 | 149 | **How do we get data from one component to another?** 150 | 151 | We pass it via props. 152 | 153 | First, we need to display the `` component. We don't always want to show the results component, just on the home page and the search page. 154 | 155 | Now, normally we would just render the component like this: 156 | 157 | ```html 158 | 159 | ``` 160 | 161 | **A few things to note here: ** 162 | 163 | 1. `` is our results component that we make 164 | 2. `{...this.state}` take our entire state from `
` and passes it down to ``. This is called an Object Spread and is used quite a bit in React. 165 | 3. It's important to note that it's not ideal to pass everything down to children _unless you need it_. If you would like to cherry pick pieces of state, we could pass them down like so: 166 | 167 | ```html 168 | 169 | ``` 170 | 171 | If you look at the Results component in React dev tools, you'll see that the beers are available under props. Search for a `` component in dev tools and you'll see 172 | 173 | ![](http://wes.io/hkVG/content) 174 | 175 | **Why props?** When something is passed down from a parent (`
`) to a Child (``), it is passed down via props. So we surface it via `this.props.beers` inside the component that receives it. 176 | 177 | In `Results.js`, let's see what we are dealing with. Dump your beers with this: 178 | 179 | ```html 180 |
{JSON.stringify(this.props.beers,null,'  ')}
181 | ``` 182 | 183 | We can see the data, but we need to loop over all the results and then display them. Remember this image? 184 | 185 | ![](http://wes.io/fcCO/content) 186 | 187 | JSX does not have any logic to handle the looping over the beers - we need to use a JavaScript map to loop over each one and return yet another component — `` for each. 188 | 189 | First import the Beer component that will be used to display each of the listed beers: 190 | 191 | ```js 192 | import Beer from "./Beer"; 193 | ``` 194 | 195 | Then we loop over each beer and return a beer component. 196 | 197 | ```html 198 |
199 | {this.props.beers.map(details => )} 200 |
201 | ``` 202 | 203 | A few things to note here: 204 | 205 | 1. `this.props.beers` is an array so we can use map with an ES6 arrow function 206 | 2. We pass the each beer result to the `` component via a details prop 207 | 3. Each time you have multiple of the same component you need to supply a `key` prop to help React differentiate them in the DOM 208 | -------------------------------------------------------------------------------- /notes/09 - Caching Beers in LocalStorage.md: -------------------------------------------------------------------------------- 1 | ## Caching Beers in LocalStorage 2 | 3 | You may notice that everytime we go back to the main page we need to re-load all the beers with our Ajax endpoint - and this is pretty slow. 4 | 5 | We can store the beer data in localStorage and when someone searches for "hops" or "ale", we will first check to see if we have the results in localStorage. 6 | 7 | In `Main.js`, above let's update our `loadBeers()` method 8 | 9 | ```diff 10 | loadBeers = (searchTerm = 'hops') => { 11 | + // Check for beers in local storage 12 | + const localStorageBeers = localStorage.getItem(`search-${searchTerm}`); 13 | + if (localStorageBeers) { 14 | + const localBeers = JSON.parse(localStorageBeers); 15 | + this.setState({ beers: localBeers, loading: false }); 16 | + return; // stop before fetch happens! 17 | + } 18 | 19 | fetch(`http://api.react.beer/v2/search?q=${searchTerm}&type=beer`).then(data => data.json()) 20 | .then((beers) => { 21 | console.log(beers); 22 | // filter for beers with images 23 | this.state.beers = beers.data.filter(beer => !!beer.labels); 24 | this.setState({beers : this.state.beers, loading:false }); 25 | + // save to local storage in case we search for this again 26 | + localStorage.setItem(`search-${searchTerm}`, JSON.stringify(this.state.beers)); 27 | }) 28 | .catch(err => console.log(err)) 29 | ``` 30 | -------------------------------------------------------------------------------- /notes/10 - Working in Beer-js.md: -------------------------------------------------------------------------------- 1 | ## Working in Beer.js 2 | 3 | So far we have passed all beers from `Main.js` → `Results.js` and passed each individual beer from `Results.js` → `Beer.js`. 4 | 5 | Working in `Beer.js`, how do we access the info about that beer? How did we pass it? **Props!** What was it called? **details**! 6 | 7 | So how do we access it inside `Beer.js`? `this.props.details`! 8 | 9 | First, let's import a few dependencies: 10 | 11 | ```js 12 | import slug from 'slugify'; 13 | import { Link } from 'react-router-dom'; 14 | ``` 15 | 16 | Then work through the render function together: 17 | 18 | ```html 19 | render() { 20 | const { name, labels, id } = this.props.details; 21 | const image = labels ? labels.medium : 'notfound.jpg'; 22 | 23 | return ( 24 |
25 | 26 |

{name}

27 | {name} 28 | 29 |
30 | ); 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /notes/11 - Adding a Loading State.md: -------------------------------------------------------------------------------- 1 | ## Introducing a loading State 2 | 3 | A common task in applications is to show a loading indicator. We can use use a boolean stored on the state of `
`. 4 | 5 | ```diff 6 | constructor() { 7 | this.state = { 8 | numBeers : 10, 9 | beers: [], 10 | + loading: true 11 | } 12 | } 13 | ``` 14 | 15 | Then update our state inside the `loadBeers` method: 16 | 17 | ```diff 18 | loadBeers = (searchTerm = 'hops') => { 19 | + this.setState({ loading: true }); 20 | 21 | // Check for beers in local storage 22 | const localStorageBeers = localStorage.getItem(`search-${searchTerm}`); 23 | if (localStorageBeers) { 24 | const localBeers = JSON.parse(localStorageBeers); 25 | this.setState({ beers: localBeers, loading: false }); 26 | return; // stop before fetch happens! 27 | } 28 | 29 | fetch(`http://api.react.beer/v2/search?q=${searchTerm}&type=beer`).then(data => data.json()) 30 | .then((beers) => { 31 | console.log(beers); 32 | // filter for beers with images 33 | const filteredBeers = beers.data.filter(beer => !!beer.labels); 34 | + this.setState({ beers: filteredBeers, loading: false }); 35 | // save to local storage in case we search for this again 36 | localStorage.setItem(`search-${searchTerm}`, JSON.stringify(this.state.beers)); 37 | }) 38 | .catch(err => console.error(err)); 39 | } 40 | 41 | ``` 42 | 43 | Now in `Results.js` we can first check if the results are loading and display the `` accordingly: 44 | 45 | Require it: 46 | 47 | ```js 48 | import Loader from './Loader'; 49 | ``` 50 | 51 | and amend your render: 52 | 53 | ```diff 54 | render() { 55 | + if(this.props.loading) { 56 | + return 57 | + } 58 | 59 | return ( 60 |
61 |
62 | {this.props.beers.map((details, i) => )} 63 |
64 |
65 | ) 66 | } 67 | ``` 68 | 69 | Finally, you can update your Loader.js to take the message prop: 70 | 71 | ```diff 72 | -

Your Message from props goes here

73 | +

{this.props.message}

74 | ``` 75 | -------------------------------------------------------------------------------- /notes/12 - Exercise- Working with a Single-js.md: -------------------------------------------------------------------------------- 1 | ## Exercise: Working with a Single.js 2 | 3 | When you click on a beer you should see this: 4 | 5 | ![](http://wes.io/fdWa/content) 6 | 7 | We need to transform Single.js to display all the info about that beer. We are going to build Single.js as an exercise where you work with each other. Here are some notes: 8 | 9 | 1. Your Single.js will have 2 items in state: `beer` and `loading` 10 | 2. You will fetch the beer's info in a `loadBeer` method that you create for `Single.js` from `http://api.react.beer/v2/beer/${beerId}`.For example 11 | 3. You can access the URL variables (Like `beerId`) in `this.props.match.params` 12 | 4. Display the Loader with the words "Pouring a cold one!" while the Ajax request is working 13 | 5. Display at least the Beer name and the Beer description. We will work through the other bits together. 14 | 15 | The final output will look like this: 16 | 17 | ![](http://wes.io/fdsT/content) 18 | -------------------------------------------------------------------------------- /notes/13 - Working with Forms + Search.md: -------------------------------------------------------------------------------- 1 | ## Working with Forms + Search 2 | 3 | The final piece of the puzzle is the search form. Let's start off by creating the markup for it in `Search.js` 4 | 5 | ```html 6 |
7 |
8 | 9 | 10 |
11 |
12 | ``` 13 | 14 | The only thing that may seem out of place here so far is the `ref={(q) => this.q = q}` — we will reference this in just a second. 15 | 16 | When someone submits that form, we need to: 17 | 18 | 1. Stop the form from submitting 19 | 2. Get the value of the input 20 | 3. redirect them to `/search/whatever-they-searched-for` 21 | 22 | Let's take these three things and tackle them step by step: 23 | 24 | ### Stop the form from submitting 25 | 26 | How would you normally stop a form from submitting? 27 | 28 | You listen to for the `submit` event and call `preventDefault` on the event! 29 | 30 | So, we will modify our form tag: `
` 31 | 32 | and then create the `handleSubmit` method on our Search component: 33 | 34 | ```js 35 | handleSubmit = e => { 36 | // 1. Stop the form from submitting 37 | e.preventDefault(); 38 | // 2. Get the value of the input 39 | // 3. redirect them to `/search/whatever-they-searched- 40 | }; 41 | ``` 42 | 43 | ### Get the value of the input 44 | 45 | There are two ways to access values of an input in react: 46 | 47 | 1. Listen for `onChange` and set that value to state by pulling the value out of the event.target.value. This is what is called a "Controlled Component" 48 | 2. Attach a ref to the `` so you can access the dom node. 49 | 50 | Since we only have 1 input, and only need the value once, we will go with #2. 51 | 52 | To create a ref, we first make one on our component with `React.createRef()`: 53 | 54 | ```js 55 | class Search extends React.Component { 56 | // ... 57 | searchRef = React.createRef(); 58 | // ... 59 | } 60 | ``` 61 | 62 | Then we assign that ref to a DOM node: 63 | 64 | ```html 65 | 66 | ``` 67 | 68 | Then, to get the value of that input: 69 | 70 | ```js 71 | const searchTerm = this.searchRef.current.value; 72 | ``` 73 | 74 | ### redirect them to `/search/whatever-they-searched-for` 75 | 76 | The final step is to change the URL. Rather than set the url ourselves, we use one of the methods on the router. In order to access the router we need to expose to the component using something called `context`. 77 | 78 | Context is a third way in which components can pass down data from the parent to the child. Since the Router is our top level component, we can access it many levels deeper, but only if we define the context. 79 | 80 | First, add this to your Search component: 81 | 82 | ```js 83 | static contextTypes = { 84 | router: PropTypes.object.isRequired 85 | } 86 | ``` 87 | 88 | And then use it's `push` method to update the browser's URL: 89 | 90 | ```js 91 | this.context.router.history.push(`/search/${searchTerm}`); 92 | ``` 93 | 94 | ## Updating our Component 95 | 96 | Now when you search for something like `angry`, you should see the URL bar update, but our component doesn't yet know how to perform the search. 97 | 98 | Where does the searching for beers happen? In our `Main.js` file with the `loadBeers()` method! 99 | 100 | So the question is, how do we re-run the `loadBeers()` method? 101 | 102 | We need to modify our componentDidMount() to first check if we have a param in the URL. 103 | 104 | ```js 105 | componentDidMount() { 106 | console.log(`mounting`); 107 | const params = this.props.match.params || {}; 108 | const searchTerm = params.searchTerm || undefined; 109 | this.loadBeers(searchTerm); 110 | } 111 | ``` 112 | 113 | And with that in place, when someone goes to `/search/angry`. The `
` `componentDidMount()` 114 | 115 | 1. Ajax request 116 | 2. the loader component will show 117 | 3. the state will be updated 118 | 4. the new beers will be displayed 119 | 120 | ## `
` → `
` Router Bug / Feature 121 | 122 | One bug (feature?!) that React Router 4 currently has is that it does not trigger a re-render of our `
` component when you search from a page a search page. 123 | 124 | What? 125 | 126 | If we are on a page where the URL is `http://localhost:3000/search/angry` 127 | 128 | And then search for `happy` which changes the url to `http://localhost:3000/search/happy` 129 | 130 | This does trigger a route change, but since this is our route: 131 | 132 | `` 133 | 134 | The "Single" component is already mounted. So to catch this change, we need another lifecycle method called `componentDidUpdate` which fires not when the component is mounted, but when it's props or state are updated. It passes up a copy of the old props and old state so we can decide if we need to re-run our search. 135 | 136 | What props get updated? The router params! 137 | 138 | ```js 139 | componentDidUpdate(prevProps) { 140 | console.log('did update'); 141 | const currentSearchTerm = this.props.match.params.searchTerm; 142 | const oldSearchTerm = prevProps.match.params.searchTerm; 143 | if (currentSearchTerm !== oldSearchTerm) { 144 | this.loadBeers(currentSearchTerm); 145 | } 146 | } 147 | ``` 148 | -------------------------------------------------------------------------------- /notes/14 - Deploying + Challenges.md: -------------------------------------------------------------------------------- 1 | # Deploying 2 | 3 | To generate a bundle ready for production, simply run `npm build`. This will generate an index.html file and a .js file that contains all your minified code. 4 | 5 | # Challenge Exercises 6 | 7 | ## Stateless Functional Components 8 | 9 | Sometimes we have components that _only include a render()_. There are no custom methods, no lifecycle hooks, though they may have propTypes. 10 | 11 | If this is the case, we use a simple function that simply returns some JSX. 12 | 13 | Let's take out Beer.js component: 14 | 15 | ```js 16 | class Beer extends React.Component { 17 | render() { 18 | const { name, labels, id } = this.props.details; 19 | const image = labels ? labels.medium : 'null.jpg'; 20 | 21 | return ( 22 |
23 | 24 |

{name}

25 | {name} 26 | 27 |
28 | ); 29 | } 30 | }; 31 | ``` 32 | 33 | We can convert that to a function that returns some JSX. Notice how we passed in props and replaced `this.props` with just `props`? 34 | 35 | ```js 36 | const Beer = function(props) { 37 | 38 | const { name, labels, id } = props.details; 39 | const image = labels ? labels.medium : 'null.jpg'; 40 | 41 | return ( 42 |
43 | 44 |

{name}

45 | {name} 46 | 47 |
48 | ); 49 | }; 50 | ``` 51 | 52 | You'll often also see it written with a one line arrow function with an implicit return. 53 | 54 | ```js 55 | const Hello = (props) => ( 56 |
57 |

Hello {props.name}!

58 |
59 | ) 60 | ``` 61 | 62 | **Challenge:** 63 | 64 | ## Cache Single Beers 65 | 66 | Can you update the `Single.js` file to cache the single beer in localStorage? If I visit the same beer page twice, it should fetch it once. 67 | 68 | ## Modify It - Make it your own 69 | 70 | Now that you have a feel for React.js, what else could you build with it? This workshop should give you head start and the only way to continue learning is to build something that you are invested in. 71 | 72 | What can you build? 73 | 74 | * Use your own API to display it's data 75 | * Add a note taking feature to the beers 76 | * Build a shopping cart from scratch 77 | * re-do your WordPress website with the WP API 78 | 79 | 80 | -------------------------------------------------------------------------------- /notes/top.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | -------------------------------------------------------------------------------- /react-workshop.md: -------------------------------------------------------------------------------- 1 | # An Intro to React Workshop 2 | 3 | This is a 1 day, 6-8 hour workshop aimed at understanding the fundamentals of React. Throughout the day we will work to incrementally build an application touching all every major point of React: 4 | 5 | * Creating Components and writing HTML with JSX 6 | * Routing with React Router 7 | * Understanding State and holding data 8 | * Passing data between components with with Props 9 | * Fetching data from an Ajax endpoint 10 | * Persisting data with LocalStorage 11 | * Working with Events in React 12 | * Working with Forms, retrieving data from DOM inputs 13 | * Stateless Functional Components 14 | * React Deployment 15 | 16 | Together we will build , an interactive Beer explorer single page application. Along the way we will learn each fundamental concept, take time to implement it in our application and understand both the _Whys_ and _hows_ of the way React works. 17 | 18 | Students are expected to have Beginner to intermediate JavaScript skills — you should know how functions, variables, arguments and all the basics of JavaScript work. 19 | 20 | ## What To Bring 21 | 22 | Bring a laptop with: 23 | 24 | * the latest version of Node.js Installed - you can download the installer over at Nodejs.org. If you aren't sure if you have node installed, open a terminal window and type `node -v`, compare that against the latest version at http://nodejs.org 25 | * A terminal / command line application. For Windows I recommend [cmder](http://cmder.net/), though the built in one will work just fine. For OSX built in Terminal, [iTerm2](https://www.iterm2.com/) or [Hyper.app]https://hyperterm.now.sh/) will work as well. 26 | * A text editor. You may use any editor but here are some good ones: 27 | * Sublime Text with [Babel-Sublime](https://github.com/babel/babel-sublime) syntax highlighter installed 28 | * [Atom](https://atom.io/) 29 | 30 | 31 | ## About Wes Bos 32 | Wes Bos is a full stack developer and teacher from Hamilton, Canada. With a knack for breaking down technical concepts and converting them into fun, digestable bits, Wes creates some of the best online courses related to web development. He is the author or [React For Beginners](http://ReactforBeginners.com), [ES6 for Everyone](https://ES6.io), [What the Flexbox?!][http://flexbox.io] and [many other courses](http://wesbos.com/courses). You can catch Wes posting 🔥 Hot Tips on his [twitter](https://twitter.com/wesbos) where he is most active. 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # React Workshop 2 | 3 | Hey Everyone - Wes here. I'll be your instructor for the react workshop. I need you to do a few things before you come to the workshop. 4 | 5 | ## Getting Started 6 | 7 | 1. Make sure you have the latest version of Node.js installed. If you don't have it, or are an older version, you can visit Nodejs.org and download the installer. 8 | 2. Download the starter files + notes: 9 | * Download the zip here: https://github.com/wesbos/React-Beer-Me/archive/master.zip 10 | 3. Have a terminal application ready. The built in Mac OS terminal works just great though I find Windows users prefer to use CMDER - 11 | 4. `npm install` all dependencies before you come. cd into your `starter-files` directory and type `npm install`. If you aren't comfortable in the terminal, you may wait until the morning of the workshop to get a hand with this. 12 | 5. Make sure your text editor is setup to handle React. You may use any editor but here are some good ones: 13 | * [VS Code](https://code.visualstudio.com/) with the default JavaScript highlighter. 14 | * Sublime Text with [Babel-Sublime](https://github.com/babel/babel-sublime) syntax highlighter installed 15 | * [Atom](https://atom.io/) with the default JavaSCript 16 | 6. Install the React Dev Tools browser extension for either Chrome or Firefox - they are both available in their respective stores. 17 | 18 | 19 | If there are any questions, feel free to email me! wes@wesbos.com 20 | 21 | -------------------------------------------------------------------------------- /starter-files/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/.DS_Store -------------------------------------------------------------------------------- /starter-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beerme", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-scripts": "1.1.0" 7 | }, 8 | "dependencies": { 9 | "prop-types": "^15.6.0", 10 | "react": "^16.2.0", 11 | "react-dom": "^16.2.0", 12 | "react-router-dom": "^4.2.2", 13 | "slugify": "^1.2.9" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /starter-files/public/css/style.styl: -------------------------------------------------------------------------------- 1 | yellow = #FFC425 2 | black = #0D181C 3 | red = #e13e45 4 | bevan = 'Bevan', cursive; 5 | 6 | 7 | html 8 | font-size 10px 9 | 10 | body 11 | font-family sans-serif 12 | background yellow 13 | background: linear-gradient(to bottom, yellow 50%,darken(yellow,20%) 100%); 14 | background-attachment fixed 15 | 16 | p 17 | font-size 1.8rem 18 | line-height 1.5 19 | 20 | a 21 | color black 22 | 23 | html 24 | box-sizing: border-box; 25 | 26 | *, *:before, *:after 27 | box-sizing: inherit; 28 | 29 | img 30 | max-width 100% 31 | 32 | h1 33 | text-align center 34 | font-family bevan 35 | font-weight normal 36 | transform rotate(-5deg) skew(-20deg) 37 | margin 2rem 0 2rem 0 38 | a 39 | text-decoration none 40 | font-size 10rem 41 | text-shadow 3px 3px 0 alpha(black, 20%) 42 | 43 | .beer 44 | background white 45 | padding 1rem 46 | border-top 1.5rem solid red 47 | flex 1 0 auto 48 | margin 2rem 49 | overflow hidden 50 | width calc(33.3% - 4rem) 51 | box-shadow 0 0 10px alpha(black, 20%) 52 | transition all 0.2s 53 | &:hover 54 | transform scale(1.05) 55 | box-shadow 0 0 20px 2px alpha(black, 30%) 56 | a 57 | text-decoration none 58 | h2 59 | margin 0 60 | font-size 1.5rem 61 | text-align center 62 | margin-bottom 2rem 63 | 64 | .beers 65 | display flex 66 | flex-wrap wrap 67 | max-width 900px 68 | margin 0 auto 69 | 70 | 71 | /* 72 | Search 73 | */ 74 | 75 | .search 76 | text-align center 77 | max-width 500px 78 | margin 2rem auto 79 | form 80 | display flex 81 | & > * 82 | flex 1 83 | background white 84 | border 0 85 | padding 2rem 86 | font-size 2rem 87 | &[type="submit"] 88 | flex-grow 2 89 | background red 90 | color white 91 | text-transform uppercase 92 | &[type="text"] 93 | flex-grow 10 94 | 95 | /* 96 | Loader 97 | */ 98 | 99 | .loader 100 | margin 0 auto 101 | text-align center 102 | font-size 3rem 103 | font-weight 100 104 | 105 | 106 | /* 107 | Single Beer View 108 | */ 109 | 110 | .single-beer 111 | max-width 500px 112 | margin 0 auto 113 | background white 114 | padding 2rem 115 | .label 116 | width 100% 117 | img 118 | max-width 100% 119 | 120 | h3 121 | font-size 2rem 122 | 123 | .glass 124 | img 125 | float left 126 | width 200px 127 | -------------------------------------------------------------------------------- /starter-files/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/favicon.ico -------------------------------------------------------------------------------- /starter-files/public/images/ball.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /starter-files/public/images/glass-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-1.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-10.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-13.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-14.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-2.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-3.JPG -------------------------------------------------------------------------------- /starter-files/public/images/glass-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-4.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-5.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-6.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-7.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-8.jpg -------------------------------------------------------------------------------- /starter-files/public/images/glass-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/public/images/glass-9.jpg -------------------------------------------------------------------------------- /starter-files/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | React App 18 | 19 | 20 |
21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /starter-files/src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/React-Beer-Me/242f6381c0a918fae1bec993e22fcf97c58dd95b/starter-files/src/components/.gitkeep -------------------------------------------------------------------------------- /starter-files/src/index.js: -------------------------------------------------------------------------------- 1 | // Let's Go! 2 | -------------------------------------------------------------------------------- /starter-files/src/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 10px; 3 | } 4 | 5 | body { 6 | font-family: sans-serif; 7 | background: #ffc425; 8 | background: linear-gradient(to bottom, #ffc425 50%, #eaaa00 100%); 9 | background-attachment: fixed; 10 | } 11 | 12 | p { 13 | font-size: 1.8rem; 14 | line-height: 1.5; 15 | } 16 | 17 | a { 18 | color: #0d181c; 19 | } 20 | 21 | html { 22 | box-sizing: border-box; 23 | } 24 | 25 | *, 26 | *:before, 27 | *:after { 28 | box-sizing: inherit; 29 | } 30 | 31 | img { 32 | max-width: 100%; 33 | } 34 | 35 | h1 { 36 | text-align: center; 37 | font-family: 'Bevan', cursive; 38 | font-weight: normal; 39 | transform: rotate(-5deg) skew(-20deg); 40 | margin: 2rem 0 2rem 0; 41 | } 42 | 43 | h1 a { 44 | text-decoration: none; 45 | font-size: 10rem; 46 | text-shadow: 3px 3px 0 rgba(13,24,28,0.2); 47 | } 48 | 49 | .beer { 50 | background: #fff; 51 | padding: 1rem; 52 | border-top: 1.5rem solid #e13e45; 53 | flex: 1 0 auto; 54 | margin: 2rem; 55 | overflow: hidden; 56 | width: calc(33.3% - 4rem); 57 | box-shadow: 0 0 10px rgba(13,24,28,0.2); 58 | transition: all 0.2s; 59 | } 60 | 61 | .beer:hover { 62 | transform: scale(1.05); 63 | box-shadow: 0 0 20px 2px rgba(13,24,28,0.3); 64 | } 65 | 66 | .beer a { 67 | text-decoration: none; 68 | } 69 | 70 | .beer h2 { 71 | margin: 0; 72 | font-size: 1.5rem; 73 | text-align: center; 74 | margin-bottom: 2rem; 75 | } 76 | 77 | .beers { 78 | display: flex; 79 | flex-wrap: wrap; 80 | max-width: 900px; 81 | margin: 0 auto; 82 | } 83 | /* 84 | Search 85 | */ 86 | .search { 87 | text-align: center; 88 | max-width: 500px; 89 | margin: 2rem auto; 90 | } 91 | 92 | .search form { 93 | display: flex; 94 | } 95 | 96 | .search form > * { 97 | flex: 1; 98 | background: #fff; 99 | border: 0; 100 | padding: 2rem; 101 | font-size: 2rem; 102 | } 103 | 104 | .search form > *[type="submit"] { 105 | flex-grow: 2; 106 | background: #e13e45; 107 | color: #fff; 108 | text-transform: uppercase; 109 | } 110 | 111 | .search form > *[type="text"] { 112 | flex-grow: 10; 113 | } 114 | /* 115 | Loader 116 | */ 117 | .loader { 118 | margin: 0 auto; 119 | text-align: center; 120 | font-size: 3rem; 121 | font-weight: 100; 122 | } 123 | /* 124 | Single Beer View 125 | */ 126 | .single-beer { 127 | max-width: 500px; 128 | margin: 0 auto; 129 | background: #fff; 130 | padding: 2rem; 131 | } 132 | 133 | .single-beer .label { 134 | width: 100%; 135 | } 136 | 137 | .single-beer img { 138 | max-width: 100%; 139 | } 140 | 141 | .single-beer h3 { 142 | font-size: 2rem; 143 | } 144 | 145 | .glass img { 146 | float: left; 147 | width: 200px; 148 | } 149 | --------------------------------------------------------------------------------