├── .github └── workflows │ └── next.yaml ├── .gitignore ├── README.md ├── components ├── corner.js ├── footer.js ├── github.js ├── header.js ├── layout.js ├── linkedin.js └── twitter.js ├── context ├── courseInfoContext.js └── headerContext.js ├── course.json ├── data ├── course.js └── lesson.js ├── lessons ├── 01-welcome │ └── A-intro.md ├── 02-no-frills-react │ ├── A-pure-react.md │ ├── B-components.md │ └── meta.json ├── 03-js-tools │ ├── A-npm.md │ ├── B-prettier.md │ ├── C-eslint.md │ ├── D-git.md │ ├── E-parcel.md │ ├── F-babel.md │ ├── G-browserslist.md │ └── meta.json ├── 04-core-react-concepts │ ├── A-jsx.md │ ├── B-hooks.md │ ├── C-effects.md │ ├── D-custom-hooks.md │ ├── E-handling-user-input.md │ ├── F-component-composition.md │ ├── G-react-dev-tools.md │ └── meta.json ├── 05-react-capabilities │ ├── A-react-router.md │ ├── B-class-components.md │ ├── C-class-properties.md │ ├── D-managing-state-in-class-components.md │ └── meta.json ├── 06-special-case-react-tools │ ├── A-error-boundaries.md │ ├── B-context.md │ ├── C-portals-and-refs.md │ └── meta.json ├── 07-end-of-intro │ ├── A-conclusion.md │ ├── B-ways-to-expand-your-app.md │ └── meta.json ├── 08-intermediate-react-v3 │ └── A-welcome-to-intermediate-react-v4.md ├── 09-hooks-in-depth │ ├── A-usestate.md │ ├── B-useeffect.md │ ├── C-usecontext.md │ ├── D-useref.md │ ├── E-usereducer.md │ ├── F-usememo.md │ ├── G-usecallback.md │ ├── H-uselayouteffect.md │ ├── I-useimperativehandle.md │ ├── J-usedebugvalue.md │ └── meta.json ├── 10-tailwindcss │ ├── A-css-and-react.md │ ├── B-tailwind-basics.md │ ├── C-tailwind-plugins.md │ ├── D-grid-and-breakpoints.md │ ├── E-positioning.md │ └── meta.json ├── 11-code-splitting │ ├── A-code-splitting.md │ └── meta.json ├── 12-server-side-rendering │ ├── A-server-side-rendering.md │ ├── B-streaming-markup.md │ └── meta.json ├── 13-typescript │ ├── A-refactor-modal.md │ ├── B-typescript-and-eslint.md │ ├── C-refactor-theme-context.md │ ├── D-refactor-details.md │ ├── E-refactor-error-boundary.md │ ├── F-refactor-carousel.md │ ├── G-refactor-pet.md │ ├── H-refactor-breed-list.md │ ├── I-refactor-search-params.md │ ├── J-refactor-results.md │ ├── K-refactor-app.md │ └── meta.json ├── 14-redux │ ├── A-redux.md │ ├── B-reducers.md │ ├── C-action-creators.md │ ├── D-providers.md │ ├── E-dispatching-actions.md │ ├── F-redux-dev-tools.md │ └── meta.json ├── 15-testing │ ├── A-testing-react.md │ ├── B-basic-react-testing.md │ ├── C-testing-ui-interactions.md │ ├── D-testing-custom-hooks.md │ ├── E-mocks.md │ ├── F-snapshots.md │ ├── G-istanbul.md │ └── meta.json └── 16-end-of-intermediate │ ├── A-end-of-intermediate.md │ └── meta.json ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── index.js └── lessons │ └── [section] │ └── [slug].js ├── public ├── .nojekyll └── images │ ├── BRAND-WHearts.png │ ├── CLASS-WALLPAPER-A.jpg │ ├── CLASS-WALLPAPER-B.jpg │ ├── HEADER-A.png │ ├── TOC-A.png │ ├── Wordmark-XL.png │ ├── author.jpg │ ├── brian.jpg │ ├── course-icon.png │ ├── github-social.svg │ ├── linkedin-social.svg │ ├── social-share-cover.jpg │ └── twitter-social.svg └── styles ├── courses.css ├── footer.css └── variables.css /.github/workflows/next.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy NextJS Site to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: npm install, export 14 | run: | 15 | npm install 16 | npm run export 17 | - name: Deploy site to gh-pages branch 18 | uses: crazy-max/ghaction-github-pages@v2 19 | with: 20 | target_branch: gh-pages 21 | build_dir: out 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | For Frontend Master, WIP 2 | -------------------------------------------------------------------------------- /components/corner.js: -------------------------------------------------------------------------------- 1 | export default function Corner() { 2 | return ( 3 |
4 | 11 | 12 | 13 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 42 | 43 | 44 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /components/footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Gh from "./github"; 3 | import Tw from "./twitter"; 4 | import Li from "./linkedin"; 5 | 6 | export default function Footer({ twitter, linkedin, github }) { 7 | return ( 8 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /components/github.js: -------------------------------------------------------------------------------- 1 | export default function GitHub() { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import Link from "next/link"; 3 | import { Context as HeaderContext } from "../context/headerContext"; 4 | import { Context as CourseContext } from "../context/courseInfoContext"; 5 | 6 | export default function Header(props) { 7 | const [{ section, title, icon }] = useContext(HeaderContext); 8 | const { frontendMastersLink } = useContext(CourseContext); 9 | return ( 10 |
11 |

12 | {props.title} 13 |

14 |
15 | {frontendMastersLink ? ( 16 | 17 | Watch on Frontend Masters 18 | 19 | ) : null} 20 | {section ? ( 21 |

22 | {section} {title} 23 |

24 | ) : null} 25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import Footer from "./footer"; 4 | import Header from "./header"; 5 | import getCourseConfig from "../data/course"; 6 | import { Provider as HeaderProvider } from "../context/headerContext"; 7 | import { Provider as CourseInfoProvider } from "../context/courseInfoContext"; 8 | 9 | function Layout({ children }) { 10 | const courseInfo = getCourseConfig(); 11 | const headerHook = useState({}); 12 | return ( 13 | 14 | 15 |
16 |
17 |
18 |
{children}
19 |
20 |
25 |
26 |
27 |
28 | ); 29 | } 30 | 31 | export default function App({ children }) { 32 | return {children}; 33 | } 34 | -------------------------------------------------------------------------------- /components/linkedin.js: -------------------------------------------------------------------------------- 1 | export default function LinkedIn() { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /components/twitter.js: -------------------------------------------------------------------------------- 1 | export default function Twitter() { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /context/courseInfoContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const courseInfoContext = createContext([{}, () => {}]); 4 | 5 | export const Provider = courseInfoContext.Provider; 6 | export const Consumer = courseInfoContext.Consumer; 7 | export const Context = courseInfoContext; 8 | -------------------------------------------------------------------------------- /context/headerContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | const headerContext = createContext([{}, () => {}]); 4 | 5 | export const Provider = headerContext.Provider; 6 | export const Consumer = headerContext.Consumer; 7 | export const Context = headerContext; 8 | -------------------------------------------------------------------------------- /course.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Brian Holt", 4 | "company": "Stripe" 5 | }, 6 | "title": "Complete Intro to React v6", 7 | "subtitle": "and Intermediate React v3", 8 | "frontendMastersLink": "https://frontendmasters.com/courses/complete-react-v6/", 9 | "social": { 10 | "linkedin": "btholt", 11 | "github": "btholt", 12 | "twitter": "holtbt" 13 | }, 14 | "description": "The Complete Intro to React v6, rewritten in Next.js", 15 | "keywords": ["react", "javascript", "Frontend Masters", "Brian Holt"], 16 | "productionBaseUrl": "/next-react-v6" 17 | } 18 | -------------------------------------------------------------------------------- /data/course.js: -------------------------------------------------------------------------------- 1 | import config from "../course.json"; 2 | 3 | const DEFAULT_CONFIG = { 4 | author: { 5 | name: "An Author", 6 | company: "An Author's Company", 7 | }, 8 | title: "A Superb Course", 9 | subtitle: "That Teaches Nice Things", 10 | frontendMastersLink: "", 11 | description: "A nice course for nice people.", 12 | keywords: ["a nice course", "for people", "to learn", "nice things"], 13 | social: { 14 | linkedin: "btholt", 15 | github: "btholt", 16 | twitter: "holtbt", 17 | }, 18 | productionBaseUrl: "/", 19 | }; 20 | 21 | export default function getCourseConfig() { 22 | return Object.assign({}, DEFAULT_CONFIG, config); 23 | } 24 | -------------------------------------------------------------------------------- /data/lesson.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs/promises"; 3 | import parseFrontMatter from "front-matter"; 4 | import { titleCase } from "title-case"; 5 | import { marked } from "marked"; 6 | import hljs from "highlight.js"; 7 | 8 | marked.setOptions({ 9 | baseUrl: process.env.BASE_URL ? process.env.BASE_URL + "/" : "/", 10 | highlight: function (code, lang) { 11 | const language = hljs.getLanguage(lang) ? lang : "plaintext"; 12 | return hljs.highlight(code, { language }).value; 13 | }, 14 | langPrefix: "hljs language-", 15 | }); 16 | 17 | const DEFAULT_ICON = "info-circle"; 18 | const lessonsPath = path.join(process.env.ROOT, "lessons"); 19 | 20 | function getTitle(slug, override) { 21 | let title = override; 22 | if (!title) { 23 | title = titleCase(slug.split("-").join(" ")); 24 | } 25 | 26 | return title; 27 | } 28 | 29 | async function getMeta(section) { 30 | let meta = {}; 31 | try { 32 | const file = await fs.readFile( 33 | path.join(lessonsPath, section, "meta.json") 34 | ); 35 | meta = JSON.parse(file.toString()); 36 | } catch (e) { 37 | // no meta.json, nothing to do 38 | } 39 | 40 | return meta; 41 | } 42 | 43 | function slugify(inputPath) { 44 | const pathParts = inputPath.split("-"); 45 | const pathOrder = pathParts.shift(); 46 | const pathSlug = pathParts.join("-"); 47 | return { 48 | slug: pathSlug, 49 | order: pathOrder, 50 | title: titleCase(pathParts.join(" ")), 51 | }; 52 | } 53 | 54 | export async function getLessons() { 55 | const dir = await fs.readdir(lessonsPath); 56 | const sections = []; 57 | 58 | for (let dirFilename of dir) { 59 | const dirStats = await fs.lstat(path.join(lessonsPath, dirFilename)); 60 | 61 | if (dirStats.isFile()) { 62 | continue; 63 | } 64 | 65 | const lessonsDir = await fs.readdir(path.join(lessonsPath, dirFilename)); 66 | 67 | let { 68 | title: sectionTitle, 69 | order: sectionOrder, 70 | slug: sectionSlug, 71 | } = slugify(dirFilename); 72 | 73 | let icon = DEFAULT_ICON; 74 | 75 | const meta = await getMeta(dirFilename); 76 | if (meta.title) { 77 | sectionTitle = meta.title; 78 | } 79 | if (meta.icon) { 80 | icon = meta.icon; 81 | } 82 | 83 | const lessons = []; 84 | for (let lessonFilename of lessonsDir) { 85 | if (lessonFilename.slice(-3) !== ".md") { 86 | continue; 87 | } 88 | 89 | const filePath = path.join(lessonsPath, dirFilename, lessonFilename); 90 | 91 | const file = await fs.readFile(filePath); 92 | const { attributes } = parseFrontMatter(file.toString()); 93 | let slug = lessonFilename.replace(/\.md$/, ""); 94 | 95 | const slugParts = slug.split("-"); 96 | const lessonOrder = slugParts.shift(); 97 | 98 | slug = slugParts.join("-"); 99 | 100 | const title = getTitle(slug, attributes.title); 101 | 102 | lessons.push({ 103 | slug, 104 | fullSlug: `/lessons/${sectionSlug}/${slug}`, 105 | title, 106 | order: `${sectionOrder}${lessonOrder}`, 107 | path: filePath, 108 | }); 109 | } 110 | 111 | sections.push({ 112 | icon, 113 | title: sectionTitle, 114 | slug: sectionSlug, 115 | lessons, 116 | order: sectionOrder, 117 | }); 118 | } 119 | 120 | return sections; 121 | } 122 | 123 | export async function getLesson(targetDir, targetFile) { 124 | const dir = await fs.readdir(lessonsPath); 125 | 126 | for (let i = 0; i < dir.length; i++) { 127 | const dirPath = dir[i]; 128 | if (dirPath.endsWith(targetDir)) { 129 | const lessonDir = ( 130 | await fs.readdir(path.join(lessonsPath, dirPath)) 131 | ).filter((str) => str.endsWith(".md")); 132 | 133 | for (let j = 0; j < lessonDir.length; j++) { 134 | const slugPath = lessonDir[j]; 135 | if (slugPath.endsWith(targetFile + ".md")) { 136 | const filePath = path.join(lessonsPath, dirPath, slugPath); 137 | const file = await fs.readFile(filePath); 138 | const { attributes, body } = parseFrontMatter(file.toString()); 139 | const html = marked(body); 140 | const title = getTitle(targetFile, attributes.title); 141 | const meta = await getMeta(dirPath); 142 | 143 | const section = getTitle(targetDir, meta.title); 144 | const icon = meta.icon ? meta.icon : DEFAULT_ICON; 145 | 146 | let nextSlug; 147 | let prevSlug; 148 | 149 | // get next 150 | if (lessonDir[j + 1]) { 151 | // has next in section 152 | const { slug: next } = slugify(lessonDir[j + 1]); 153 | nextSlug = `${targetDir}/${next.replace(/\.md$/, "")}`; 154 | } else if (dir[i + 1]) { 155 | // has next in next section 156 | const nextDir = ( 157 | await fs.readdir(path.join(lessonsPath, dir[i + 1])) 158 | ).filter((str) => str.endsWith(".md")); 159 | const nextDirSlug = slugify(dir[i + 1]).slug; 160 | const nextLessonSlug = slugify(nextDir[0]).slug.replace( 161 | /\.md$/, 162 | "" 163 | ); 164 | nextSlug = `${nextDirSlug}/${nextLessonSlug}`; 165 | } else { 166 | // last section 167 | nextSlug = null; 168 | } 169 | 170 | // get prev 171 | if (lessonDir[j - 1]) { 172 | // has prev in section 173 | const { slug: prev } = slugify(lessonDir[j - 1]); 174 | prevSlug = `${targetDir}/${prev.replace(/\.md$/, "")}`; 175 | } else if (dir[i - 1]) { 176 | // has prev in prev section 177 | const prevDir = ( 178 | await fs.readdir(path.join(lessonsPath, dir[i - 1])) 179 | ).filter((str) => str.endsWith(".md")); 180 | const prevDirSlug = slugify(dir[i - 1]).slug; 181 | const prevLessonSlug = slugify( 182 | prevDir[prevDir.length - 1] 183 | ).slug.replace(/\.md$/, ""); 184 | prevSlug = `${prevDirSlug}/${prevLessonSlug}`; 185 | } else { 186 | // first section 187 | prevSlug = null; 188 | } 189 | 190 | const base = process.env.BASE_URL ? process.env.BASE_URL : "/"; 191 | 192 | return { 193 | attributes, 194 | html, 195 | slug: targetFile, 196 | title, 197 | section, 198 | icon, 199 | filePath, 200 | nextSlug: nextSlug ? path.join(base, "lessons", nextSlug) : null, 201 | prevSlug: prevSlug ? path.join(base, "lessons", prevSlug) : null, 202 | }; 203 | } 204 | } 205 | } 206 | } 207 | 208 | return false; 209 | } 210 | -------------------------------------------------------------------------------- /lessons/02-no-frills-react/A-pure-react.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Brian teaches React without any frills: just you, some JavaScript, and the browser. No build step." 3 | --- 4 | 5 | Let's start by writing pure React. No compile step. No JSX. No Babel. No Webpack or Parcel. Just some JavaScript on a page. 6 | 7 | Let's start your project. Create your project directory. I'm going to call mine `adopt-me` since we're going to be building a pet adoption app throughout this course. Create an index.html and put it into a `src/` directory inside of your project folder. In index.html put: 8 | 9 | ```javascript 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Adopt Me 19 | 20 | 21 | 22 |
not rendered
23 | 24 | 25 | 28 | 29 | 30 | 31 | ``` 32 | 33 | > What's new between React 16 and React 17? Nothing! No new features were added. It was a "stepping stone" version that allows sites to upgrade React versions gradually. Previously only one copy of React could run on a page at a time and with v17 more than one can. [See more here][react17]. 34 | 35 | Now open this file in your browser. On Mac, hit ⌘ (command) + O in your favorite browser, and on Windows and Linux hit CTRL + O to open the Open prompt. Navigate to wherever you saved the file and open it. You should see a line of text saying "not rendered". 36 | 37 | - Pretty standard HTML5 document. If this is confusing, I teach another course called [Intro to Web Dev][webdev] that can help you out. 38 | - We're adding a root div. We'll render our React app here in a sec. It doesn't _have_ to be called root, just a common practice. 39 | - We have two script tags. 40 | - The first is the React library. This library is the interface of how to interact with React; all the methods (except one) will be via this library. It contains no way of rendering itself though; it's just the API. 41 | - The second library is the rendering layer. Since we're rendering to the browser, we're using React DOM. There are other React libraries like React Native, React 360 (formerly React VR), A-Frame React, React Blessed, and others. You need both script tags. The order is not important. 42 | - The last script tag is where we're going to put our code. You don't typically do this but I wanted to start as simple as possible. This script tag must come _after_ the other two. 43 | 44 | > Let's add some style! [Click here][style] to get the stylesheet for this course. If you follow along with the course and use the same class names, the styles will be applied for you automatically. This isn't a course on CSS so I make no assertion it's any good! 45 | 46 | In the last script tag, put the following. 47 | 48 | ```javascript 49 | const App = () => { 50 | return React.createElement( 51 | "div", 52 | {}, 53 | React.createElement("h1", {}, "Adopt Me!") 54 | ); 55 | }; 56 | 57 | ReactDOM.render(React.createElement(App), document.getElementById("root")); 58 | ``` 59 | 60 | This is about the simplest React app you can build. 61 | 62 | - The first thing we do is make our own component, App. React is all about making components. And then taking those components and making more components out of those. 63 | - There are two types of components, function components and class components. This is a function component. We'll see class components shortly. 64 | - A function component _must_ return markup (which is what `React.createElement` generates.) 65 | - These component render functions _have_ to be fast. This function is going to be called a lot. It's a hot code path. 66 | - Inside of the render function, you cannot modify any sort of state. Put in functional terms, this function must be pure. You don't know how or when the function will be called so it can't modify any ambient state. 67 | - `React.createElement` creates one _instance_ of some component. If you pass it a _string_, it will create a DOM tag with that as the string. We used `h1` and `div`, those tags are output to the DOM. If we put `x-some-custom-element`, it'll output that (so web components are possible too.) 68 | - The second empty object (you can put `null` too) is attributes we're passing to the tag or component. Whatever we put in this will be output to the element (like id or style.) 69 | - `ReactDOM.render` is what takes our rendered `App` component and puts in the DOM (in our case we're putting it in the `root` element.) 70 | - Notice we're using `React.createElement` with `App` as a parameter to `ReactDOM.render`. We need an _instance_ of `App` to render out. `App` is a class of components and we need to render one instance of a class. That's what `React.createElement` does: it makes an instance of a class. 71 | 72 | [webdev]: https://frontendmasters.com/courses/web-development-v2/ 73 | [logo]: https://raw.githubusercontent.com/btholt/react-redux-workshop/master/src/adopt-me.png 74 | [react17]: https://reactjs.org/blog/2020/10/20/react-v17.html 75 | [style]: https://raw.githubusercontent.com/btholt/citr-v6-project/master/01-no-frills-react/src/style.css 76 | -------------------------------------------------------------------------------- /lessons/02-no-frills-react/B-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Brian teaches React without any frills: just you, some JavaScript, and the browser. No build step." 3 | --- 4 | 5 | Now that we've done that, let's separate this out from a script tag on the DOM to its own script file (best practice.) Make a new file in your `src` directory called `App.js` and cut and paste your code into it. 6 | 7 | Modify your code so it looks like: 8 | 9 | ```javascript 10 | const Pet = () => { 11 | return React.createElement("div", {}, [ 12 | React.createElement("h1", {}, "Luna"), 13 | React.createElement("h2", {}, "Dog"), 14 | React.createElement("h2", {}, "Havanese"), 15 | ]); 16 | }; 17 | 18 | const App = () => { 19 | return React.createElement("div", {}, [ 20 | React.createElement("h1", {}, "Adopt Me!"), 21 | React.createElement(Pet), 22 | React.createElement(Pet), 23 | React.createElement(Pet), 24 | ]); 25 | }; 26 | 27 | ReactDOM.render(React.createElement(App), document.getElementById("root")); 28 | ``` 29 | 30 | > 🚨 You will be seeing a console warning `Warning: Each child in a list should have a unique "key" prop.` in your browser console. React's dev warnings are trying to help your code run faster. Basically React tries to keep track of components are swapped in order in a list and it does that by you giving it a unique key it can track. If it sees two things have swapped, it'll just move the components instead of re-rendering. 31 | 32 | - To make an element have multiple children, just pass it an array of elements. 33 | - We created a second new component, the `Pet` component. This component represents one pet. When you have distinct ideas represented as markup, that's a good idea to separate that it into a component like we did here. 34 | - Since we have a new `Pet` component, we can use it multiple times! We just use multiple calls to `React.createElement`. 35 | - In `createElement`, the last two parameters are optional. Since Pet has no props or children (it could, we just didn't make it use them yet) we can just leave them off. 36 | 37 | Okay so we can have multiple pets but it's not a useful component yet since not all pets will be Havanese dogs named Luna (even though _I_ have a Havanese dog named Luna.) Let's make it a bit more complicated. 38 | 39 | ```javascript 40 | const Pet = (props) => { 41 | return React.createElement("div", {}, [ 42 | React.createElement("h1", {}, props.name), 43 | React.createElement("h2", {}, props.animal), 44 | React.createElement("h2", {}, props.breed), 45 | ]); 46 | }; 47 | 48 | const App = () => { 49 | return React.createElement("div", {}, [ 50 | React.createElement("h1", {}, "Adopt Me!"), 51 | React.createElement(Pet, { 52 | name: "Luna", 53 | animal: "Dog", 54 | breed: "Havanese", 55 | }), 56 | React.createElement(Pet, { 57 | name: "Pepper", 58 | animal: "Bird", 59 | breed: "Cockatiel", 60 | }), 61 | React.createElement(Pet, { name: "Doink", animal: "Cat", breed: "Mix" }), 62 | ]); 63 | }; 64 | 65 | ReactDOM.render(React.createElement(App), document.getElementById("root")); 66 | ``` 67 | 68 | Now we have a more flexible component that accepts props from its parent. Props are variables that a parent (App) passes to its children (the instances of Pet.) Now each one can be different! Now that is far more useful than it was since this Pet component can represent not just Luna, but any Pet. This is the power of React! We can make multiple, re-usable components. We can then use these components to build larger components, which in turn make up yet-larger components. This is how React apps are made! 69 | 70 | > 🏁 [Click here to see the state of the project up until now: 01-no-frills-react][step] 71 | 72 | [step]: https://github.com/btholt/citr-v6-project/tree/master/01-no-frills-react 73 | -------------------------------------------------------------------------------- /lessons/02-no-frills-react/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "eye" 3 | } -------------------------------------------------------------------------------- /lessons/03-js-tools/A-npm.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "npm" 3 | description: "When putting any sort of JavaScript project together, npm is an essential tool. Brian talks about how to get started with npm." 4 | --- 5 | 6 | ## npm 7 | 8 | npm does not stand for Node Package Manager. It is, however, the package manager for Node. (They don't say what it stands for.) It also has all the packages in the front end scene. npm makes a command line tool, called `npm` as well. `npm` allows you to bring in code from the npm registry which is a bunch of open source modules that people have written so you can use them in your project. Whenever you run `npm install react` (don't do this yet), it will install the latest version of React from the registry. 9 | 10 | In order to start an npm project, run `npm init` at the root of your project. If you don't have Node.js installed, please go install that too. When you run `npm init` it'll ask you a bunch of questions. If you don't know the answer or don't care, just hit enter. You can always modify package.json later. This will allow us to get started installing and saving packages. 11 | -------------------------------------------------------------------------------- /lessons/03-js-tools/B-prettier.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Brian talks about his favorite JS tool, Prettier, a tool that that helps you maintain consistent code style with no work on the dev's part." 3 | --- 4 | 5 | ## Code Quality 6 | 7 | It's important to keep quality high when writing code. Or at least that's how I sell ESLint and Prettier to my co-workers. In reality I'm super lazy and want the machine to do as much work as possible so I can focus more on architecture and problem-solving and less on syntax and style. While there are many tools that can help you keep code quality high, these two I consider core to my workflow. 8 | 9 | [Prettier][prettier] is an amazing tool from the brain of [James Long][jlongster]. James, like many of us, was sick of having to constantly worry about the style of his code: where to stick indents, how many, when to break lines, etc etc. Coming from languages like Go, Reason, or Elm where all that is just taken care of by the tooling for the language, this quickly wears. James did something about it and made a tool to take care of it: Prettier. 10 | 11 | Prettier is a really fancy pretty printer. It takes the code you write, breaks it down in to an abstract syntax tree (AST) which is just a representation of your code. It then takes that AST, throws away all of your code style you made and prints it back out using a predefined style. While this sounds a little scary, it's actually really cool. Since you no longer have control of the style of your code, you no longer have to think about it at all. Your code is always consistent, as is the code from the rest of your team. No more bikeshedding!! As I like to put it: if your brain is a processor, you get to free up the thread of your brain that worries about code styles and readability: it just happens for you. Don't like semicolons? Don't write them! It puts them in for you. I _love_ Prettier. 12 | 13 | Need to tool around a bit with it before you trust it? [Go here][prettier-playground]. You can see how it works. 14 | 15 | Let's go integrate this into our project. It's _pretty_ easy (lol.) 16 | 17 | Either install Prettier globally `npm install --global prettier` or replace when I run `prettier` with (from the root of your project) `npx prettier`. From there, run `prettier script.js`. This will output the formatted version of your file. If you want to actually write the file, run `prettier --write script.js`. Go check script.js and see it has been reformatted a bit. I will say for non-JSX React, prettier makes your code less readable. Luckily Prettier supports JSX! We'll get to that shortly. 18 | 19 | Prettier has a few configurations but it's mostly meant to be a tool everyone uses and doesn't argue/bikeshed about the various code style rules. [Here they are][prettier-options]. I just use it as is since I'm lazy. Prettier can also understand [flow][flow] and [TypeScript][ts]. 20 | 21 | Prettier is great to use with [Visual Studio Code][vscode]. Just download [this extension][vscode-prettier]. Pro tip: set it to only run Prettier when it detects a Prettier config file. Makes it so you never have to turn it off. In order to do that, set `prettier.requireConfig` to `true` and `editor.formatOnSave` to true. 22 | 23 | So that our tool can know this is a Prettier project, we're going to create a file called `.prettierrc` and put `{}` in it. This lets everyone know this is a Prettier project that uses the default configuration. You can put other configs here if you hold strong formatting opinions. 24 | 25 | ## npm/Yarn scripts 26 | 27 | So it can be painful to try to remember the various CLI commands to run on your project. You can put CLI commands into it and then run the name of the tag and it'll run that script. Let's go see how that works. Put the following into your package.json. 28 | 29 | First run `npm install -D prettier`. `-D` means it's for development only. 30 | 31 | ```json 32 | "scripts": { 33 | "format": "prettier --write \"src/**/*.{js,jsx}\"" 34 | }, 35 | ``` 36 | 37 | Now you can run `yarn format` or `npm run format` and it will run that command. This means we don't have to remember that mess of a command and just have to remember format. Nice, right? We'll be leaning on this a lot during this course. 38 | 39 | ## Alternatives 40 | 41 | There really aren't any for Prettier. The alternative is just not to use a formatter. ESLint's `--fix` flag would be the closest thing. 42 | 43 | [jlongster]: https://twitter.com/jlongster 44 | [prettier]: https://github.com/prettier/prettier 45 | [prettier-playground]: https://prettier.io/playground/ 46 | [prettier-options]: https://prettier.io/docs/en/options.html 47 | [flow]: https://flow.org/ 48 | [prettier-ide]: https://github.com/prettier/prettier#editor-integration 49 | [ts]: https://www.typescriptlang.org/ 50 | [vscode]: https://code.visualstudio.com/?WT.mc_id=reactintro-github-brholt 51 | [vscode-prettier]: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode&WT.mc_id=reactintro-github-brholt 52 | -------------------------------------------------------------------------------- /lessons/03-js-tools/C-eslint.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "ESLint" 3 | description: "An essential part of maintaining a project a long time is discipline in coding standards and avoiding antipatterns. ESLint is a great tool that helps you do just that." 4 | --- 5 | 6 | On top of Prettier which takes of all the formatting, you may want to enforce some code styles which pertain more to usage: for example you may want to force people to never use `with` which is valid JS but ill advised to use. [ESLint][eslint] comes into play here. It will lint for this problems. 7 | 8 | First of all, run `npm install -D eslint eslint-config-prettier` to install eslint in your project development dependencies. Then you may configure its functionalities. 9 | 10 | There are dozens of preset configs for ESLint and you're welcome to use any one of them. The [Airbnb config][airbnb] is very popular, as is the standard config (both of which I taught in previous versions of this class). I'm going to use a looser one for this class: `eslint:recommended`. Let's create an `.eslintrc.json` file to start linting our project. 11 | 12 | Create this file called `.eslintrc.json`. 13 | 14 | ```json 15 | { 16 | "extends": ["eslint:recommended", "prettier"], 17 | "plugins": [], 18 | "parserOptions": { 19 | "ecmaVersion": 2021, 20 | "sourceType": "module", 21 | "ecmaFeatures": { 22 | "jsx": true 23 | } 24 | }, 25 | "env": { 26 | "es6": true, 27 | "browser": true, 28 | "node": true 29 | } 30 | } 31 | ``` 32 | 33 | This is a combination of the recommended configs of ESLint and Prettier. This will lint for both normal JS stuff as well as JSX stuff. Run `npx eslint script.js` now and you should see we have a few errors. Run it again with the `--fix` flag and see it will fix some of it for us! Go fix the rest of your errors and come back. Let's go add this to our npm scripts. 34 | 35 | ```json 36 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet", 37 | ``` 38 | 39 | **ESLint will have a bunch of errors right now. Ignore them; we'll fix them in a sec.** 40 | 41 | Worth adding three things here: 42 | 43 | - With npm scripts, you can pass additional parameters to the command if you want. Just add a `--` and then put whatever else you want to tack on after that. For example, if I wanted to get the debug output from ESLint, I could run `npm run lint -- --debug` which would translate to `eslint **/*.js --debug`. 44 | - We can use our fix trick this way: `npm run lint -- --fix`. 45 | - We're going to both JS and JSX. 46 | 47 | ESLint is a cinch to get working with [Visual Studio Code][vscode]. Just down [the extension][vscode-eslint]. 48 | 49 | ## Alternatives 50 | 51 | - [jshint][jshint] 52 | 53 | [eslint]: https://eslint.org 54 | [vscode-eslint]: https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint 55 | [airbnb]: https://github.com/airbnb/javascript 56 | [jshint]: http://jshint.com/ 57 | [vscode]: https://code.visualstudio.com/ 58 | -------------------------------------------------------------------------------- /lessons/03-js-tools/D-git.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Git is a critical part of any JS project and Brian makes sure you have it set up." 3 | --- 4 | 5 | Git is a critical part of any project and probably something many of you are already familiar with. If you haven't, be sure to initialize your project as a git repo with `git init` in the root of your project (VSCode and any other number of tools can do this as well.) 6 | 7 | If you haven't already, create a .gitignore at the root of your project to ignore the stuff we don't want to commit. Go ahead and put this in there: 8 | 9 | ``` 10 | node_modules 11 | .cache/ 12 | dist/ 13 | .env 14 | .DS_Store 15 | coverage/ 16 | .vscode/ 17 | ``` 18 | 19 | This will make it so these things won't get added to our repo. If you want more Git instruction, please check out [Nina Zakharenko's coures on Frontend Masters][nina] 20 | 21 | [nina]: https://frontendmasters.com/courses/git-in-depth/ 22 | -------------------------------------------------------------------------------- /lessons/03-js-tools/E-parcel.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Setting up a build process has historically been a big barrier to entry for most developers. Brian shows you how to set up Parcel which makes the whole process a breeze." 3 | --- 4 | 5 | [Parcel][parcel] is a great bundler for JavaScript projects. The first three revisions of this workshop all teach Webpack and end up spending a decent amount of time covering how to set it up. Webpack is a fantastic piece of technology and you should definitely consider using it for your large applications; it's been around a long time and has a lot of support. 6 | 7 | That being said, Parcel is an amazing tool with zero-config. It works with everything we want to do out of the box. Since this is a class on React and not build processes, this allows us to focus more on React. Let's go see what it can do for us. 8 | 9 | Parcel is going to accept an entry point, crawl through all of its dependencies, and output a single, complete file with all of our code in it. This means we can have large applications with many files and many dependencies. It would be an unmanageable mess. Already our React app has two components in one file: App and Pet. It'd be better if these were in separate files so it'd be easier to keep track of what was where. This is where Parcel can help us. 10 | 11 | Install Parcel by doing `npm install -D parcel@1.12.3` 12 | 13 | > Parcel has recently renamed the npm package from `parcel-bundler` to just `parcel`. 14 | 15 | Now inside of your `package.json` put: 16 | 17 | ```json 18 | "scripts" { 19 | "dev": "parcel src/index.html" 20 | } 21 | ``` 22 | 23 | Now open http://localhost:1234. You should see your site come up. The difference here is now it's being run through Parcel which means we can leverage all the features that Parcel allows us which we will explore shortly. 24 | 25 | So how does it work? We gave the entry point, which is index.html. It then reads that index.html file and finds its dependencies, which are the two React files and the one App.js file that we linked to. It's smart enough to detect that those two React files are remote so it doesn't do anything with those, but it sees that App.js is local and so it reads it and compiles its dependencies. Right now it has no dependencies so let's fix that. 26 | 27 | First let's fix the React and ReactDOM dependencies. Right now these are coming from unpkg.com. Unpkg isn't meant to serve production traffic, nor do we want the burden of loading _all_ of our future dependencies this way. Believe me, it would get messy quickly and we'd have to make a million requests to get all of them by the end (we'll install more later as we go.) Instead, it'd be better if we could pull our dependencies down from npm and include it in our bundle. Let's do that now. 28 | 29 | Run `npm install react@17.0.1 react-dom@17.0.1`. This will pull React and ReactDOM down from npm and put it in your node_modules directory. Now instead of loading them from unpkg, we can tell Parcel to include them in your main bundle. Let's do that now. 30 | 31 | Delete the two unpkg script tags in index.html 32 | 33 | Refresh the page and it still works! Now our React and ReactDOM is loading directly from our bundle instead of separate JavaScript files! Let's take this a step further. Create a new file called Pet.js and put this in there: 34 | 35 | ```javascript 36 | import React from "react"; 37 | 38 | export default function Pet({ name, animal, breed }) { 39 | return React.createElement("div", {}, [ 40 | React.createElement("h1", {}, name), 41 | React.createElement("h2", {}, animal), 42 | React.createElement("h2", {}, breed), 43 | ]); 44 | } 45 | ``` 46 | 47 | Go to App.js 48 | 49 | ```javascript 50 | // at the top, under React imports 51 | import React from "react"; 52 | import ReactDOM from "react-dom"; 53 | import Pet from "./Pet"; 54 | 55 | // remove Pet component 56 | ``` 57 | 58 | Load the page again. Still works! Now we can separate components into separate files. Parcel does more than just this but we'll get to that in future sections. 59 | 60 | ## Alternatives 61 | 62 | - [Webpack][webpack] 63 | - [Browserify][browserify] 64 | - [esbuild][esbuild] 65 | - [Rollup][rollup] 66 | 67 | [browserify]: http://browserify.org/ 68 | [webpack]: https://webpack.js.org/ 69 | [parcel]: https://parceljs.org/ 70 | [rollup]: https://www.rollupjs.org/ 71 | [esbuild]: https://esbuild.github.io/ 72 | -------------------------------------------------------------------------------- /lessons/03-js-tools/F-babel.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Typically Parcel handles all of your Babel needs out-of-the-box but the pet app project needs one specific transformation. Brian demonstrates how to set up a new Babel configuration." 3 | --- 4 | 5 | Babel is a core part of the JavaScript tooling ecosystem. At its core it is a transpilation tool: it takes that looks one way and transforms it it into a different looking set of code. One of its core uses to transform futuristic JavaScript (like ES2021) to an older version of JavaScript (like ES5 i.e. JavaScript before 2015) so that older browsers can use your newer JavaScript. Babel also handles things like JSX for us and it can handle TypeScript too. 6 | 7 | Typically Parcel handles 100% of your Babel needs and you don't have to care at all what's going on underneath the hood; its Babel config is well crafted and fits nearly all needs. Its one issue is that it can be slow to update when new capabilities come online and that's why here we're going to modify it a bit. 8 | 9 | One thing that Parcel 1 does (Parcel 2 whenever it comes out will work differently) is merge your config with their own. That means we only need to set up the one thing we need to change and leave the rest alone. Run the following command: 10 | 11 | ```bash 12 | npm install -D @babel/core@7.12.16 @babel/preset-react@7.12.13 13 | ``` 14 | 15 | You also need to install the core library from Babel if you're going to provide your own config. Don't worry, if you forget, Parcel will install it for you and update your package.json. 16 | 17 | Create a file called `.babelrc` in your home directory and put this in there: 18 | 19 | ```json 20 | { 21 | "presets": [ 22 | [ 23 | "@babel/preset-react", 24 | { 25 | "runtime": "automatic" 26 | } 27 | ] 28 | ] 29 | } 30 | ``` 31 | 32 | > Parcel 2 is coming (and has been coming for a long time.) Once v2 lands it will be very unlikely that these steps to configure will be necessary as I imagine the "automatic" config will be the default. [Check here][releases] to see what the latest releases are. 33 | 34 | The one _additional_ thing we're setting up over what's in the project already is the `automatic` configuration for the JSX transformation. We'll talk about it in the next chapter, but it allows us to omit `import React from 'react'` at the top of every JSX file. 35 | 36 | If you needed to update a `preset-env` configuration (a semi-frequent occurrence) or add another transformation, you could do that here too. 37 | 38 | [releases]: https://github.com/parcel-bundler/parcel/releases 39 | -------------------------------------------------------------------------------- /lessons/03-js-tools/G-browserslist.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Babel transforms your JS code from futuristic code to code that is understandable by older browsers. Via a package called browserslist (which Parcel installed for you) you can Babel what browsers to target." 3 | --- 4 | 5 | Babel transforms your JS code from futuristic code to code that is understandable by older browsers. Via a package called browserslist (which Parcel installed for you) you can Babel what browsers to target. 6 | 7 | Head to [browserslist.dev][dev]. 8 | 9 | Here you can see what sorts of queries you can provide to browsers and which browsers those will provide. By default Parcel uses `> .25%` which targets all browsers that have more than .25% usage. When I ran as of writing this, this would cover 92% of all Internet users. That's a pretty okay range to target depending on who your audience is. If you're targeting old government computers this won't work but if you're Airbnb and targeting mostly modern browsers you're probably okay. 10 | 11 | Since we're targeting ourselves and this is just a dev project, let's just target ourselves. Put `last 2 Chrome versions`, `last 2 Firefox versions`, `last 2 Edge versions`, `last 2 Safari versions`, or whatever you want. For the course notes I'll put `last 2 Chrome versions` since I've observed many of my students use Chrome. 12 | 13 | In your package.json, add a new top level field called `browserslist` (notice the `s`, browser**s**list): 14 | 15 | ```json 16 | { 17 | … 18 | "browserslist": [ 19 | "last 2 Chrome versions" 20 | ] 21 | } 22 | ``` 23 | 24 | > 🏁 [Click here to see the state of the project up until now: 02-js-tools][step] 25 | 26 | [step]: https://github.com/btholt/citr-v6-project/tree/master/02-js-tools 27 | [dev]: https://browserslist.dev 28 | -------------------------------------------------------------------------------- /lessons/03-js-tools/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JS Tools", 3 | "icon": "hammer" 4 | } -------------------------------------------------------------------------------- /lessons/04-core-react-concepts/A-jsx.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "JSX" 3 | description: "JSX is an essential part of writing React. Brian teaches you to leverage your newfound React knowledge and make it much easier to read with JSX" 4 | --- 5 | 6 | So far we've been writing React without JSX, something that I don't know anyone that actually does with their apps. _Everyone_ uses JSX. I show you this way so what JSX is actually doing is demystified to you. It doesn't do hardly anything. It just makes your code a bit more readable. 7 | 8 | If I write `React.createElement("h1", { id: "main-title" }, "My Website");`, what am I actually trying to have rendered out? `

My Website

`, right? What JSX tries to do is to shortcut this translation layer in your brain so you can just write what you mean. Let's convert Pet.js to using JSX. It will look like this: 9 | 10 | ```javascript 11 | // delete the import 12 | 13 | const Pet = (props) => { 14 | return ( 15 |
16 |

{props.name}

17 |

{props.animal}

18 |

{props.breed}

19 |
20 | ); 21 | }; 22 | 23 | export default Pet; 24 | ``` 25 | 26 | > 🚨 ESLint is currently failing. We'll fix it at the end. 27 | 28 | I don't know about you, but I find this far more readable. And if it feels uncomfortable to you to introduce HTML into your JavaScript, I invite you to give it a shot until the end of the workshop. By then it should feel a bit more comfortable. And you can always go back to the old way. 29 | 30 | However, now you know _what_ JSX is doing for you. It's just translating those HTML tags into `React.createElement` calls. _That's it._ Really. No more magic here. JSX does nothing else. Many people who learn React don't learn this. 31 | 32 | Notice the strange `{props.name}` syntax: this is how you output JavaScript expressions in JSX. An expression is anything that can be the right side of an assignment operator in JavaScript, e.g. `const x = `. If you take away the `{}` it will literally output `props.name` to the DOM. 33 | 34 | > Notice we don't have to do `import React from 'react'` here like we used to. The latest version of JSX handles that for you so you only need to explicitly import the React package when you need to use something from it; otherwise feel free to do JSX without having to import React! 35 | 36 | Notice you still have to import React despite React not being explicitly used. Remember that JSX is compiled to `React.createElement` calls. Anywhere you use JSX, you need to import React. 37 | 38 | So now JSX is demystified a bit, let's go convert App.js. 39 | 40 | ```javascript 41 | import { render } from "react-dom"; 42 | import Pet from "./Pet"; 43 | 44 | const App = () => { 45 | return ( 46 |
47 |

Adopt Me!

48 | 49 | 50 | 51 |
52 | ); 53 | }; 54 | 55 | render(, document.getElementById("root")); 56 | ``` 57 | 58 | > 🚨 ESLint is currently failing. We'll fix it at the end. 59 | 60 | Notice we have Pet as a component. Notice that the `P` in `Pet` is capitalized. It _must_ be. If you make it lowercase, it will try to have `pet` as a web component and not a React component. 61 | 62 | We now pass props down as we add attributes to an HTML tag. Pretty cool. 63 | 64 | ## ESLint + React 65 | 66 | We need to give ESLint a hand to get it to recognize React and not yell about React not being used. Right now it thinks we're importing React and not using because it doesn't know what to do with React. Let's help it. 67 | 68 | Run this: `npm install -D eslint-plugin-import@2.22.1 eslint-plugin-jsx-a11y@6.4.1 eslint-plugin-react@7.22.0` 69 | 70 | Update your .eslintrc.json to: 71 | 72 | ```javascript 73 | { 74 | "extends": [ 75 | "eslint:recommended", 76 | "plugin:import/errors", 77 | "plugin:react/recommended", 78 | "plugin:jsx-a11y/recommended", 79 | "prettier", 80 | "prettier/react" 81 | ], 82 | "rules": { 83 | "react/prop-types": 0, 84 | "react/react-in-jsx-scope": 0 85 | }, 86 | "plugins": ["react", "import", "jsx-a11y"], 87 | "parserOptions": { 88 | "ecmaVersion": 2021, 89 | "sourceType": "module", 90 | "ecmaFeatures": { 91 | "jsx": true 92 | } 93 | }, 94 | "env": { 95 | "es6": true, 96 | "browser": true, 97 | "node": true 98 | }, 99 | "settings": { 100 | "react": { 101 | "version": "detect" 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | This is a little more complicated config than I used in previous versions of the workshop but this is what I use in my personal projects and what I'd recommend to you. In previous versions of this workshop, I used [airbnb][airbnb] and [standard][standard]. Feel free to check those out; I now find both of them a bit too prescriptive. Linting is a very opinionated subject, so feel free to explore what you like. 108 | 109 | This particular configuration has a lot of rules to help you quickly catch common bugs but otherwise leaves you to write code how you want. 110 | 111 | - The import plugin helps ESLint catch commons bugs around imports, exports, and modules in general 112 | - jsx-a11y catches many bugs around accessibility that can accidentally arise using React, like not having an `alt` attribute on an `img` tag. 113 | - react is mostly common React bugs like not calling one of your props children. 114 | - `eslint-plugin-react` now requires you to inform of it what version of React you're using. We're telling it here to look at the package.json to figure it out. 115 | - `"react/react-in-jsx-scope": 0` is new since you used to have to import React everywhere but now with the recent revision of React you don't need to. 116 | 117 | Now your project should pass lint. 118 | 119 | > 🏁 [Click here to see the state of the project up until now: 03-jsx][step] 120 | 121 | [airbnb]: https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb 122 | [standard]: https://standardjs.com/ 123 | [step]: https://github.com/btholt/citr-v6-project/tree/master/03-jsx 124 | -------------------------------------------------------------------------------- /lessons/04-core-react-concepts/C-effects.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "useEffect is a criticl hook for React, allowing developers to do asynchronous actions like making HTTP requests" 3 | --- 4 | 5 | We have enough to start making some requests now. We want the app to request an initial set of pets on initial load of the page. So let's make that happen using a special hook called `useEffect`. `useEffect` allows you to say "do a render of this component first so the user can see _something_ then as soon as the render is done, _then_ do something (the something here being an effect.) In our case, we want the user to see our UI first then we want to make a request to the API so we can that initial list of pets. 6 | 7 | Add this to SearchParams.js 8 | 9 | ```javascript 10 | // change import at top 11 | import { useEffect, useState } from "react"; 12 | import Pet from "./Pet"; 13 | 14 | // add to the other useStates inside component at top 15 | const [pets, setPets] = useState([]); 16 | 17 | // add inside component, beneath all the `useState` setup 18 | useEffect(() => { 19 | requestPets(); 20 | }, []); // eslint-disable-line react-hooks/exhaustive-deps 21 | 22 | async function requestPets() { 23 | const res = await fetch( 24 | `http://pets-v2.dev-apis.com/pets?animal=${animal}&location=${location}&breed=${breed}` 25 | ); 26 | const json = await res.json(); 27 | 28 | setPets(json.pets); 29 | } 30 | 31 | // in jsx, under form, inside the larger div 32 | { 33 | pets.map((pet) => ( 34 | 35 | )); 36 | } 37 | ``` 38 | 39 | > 🚨 If you're seeing an error about regeneratorRuntime, please go back to the Browserslist lesson and make sure you followed that. If you have and it's still broke, please delete your node_modules, .cache, and dist directories, run `npm install` again, and try running `npm run dev` again. 40 | 41 | - We're taking advantage of closures here that if we define the requestPets function _inside_ of the render that it will have access to that scope and can use all the hooks there. 42 | - We could have actually put requestPets inside of the effect but we're going to use it again here in a sec with the submit button. 43 | - the `[]` at the end of the useEffect is where you declare your data dependencies. React wants to know _when_ to run that effect again. You don't give it data dependencies, it assumes any time any hook changes that you should run the effect again. This is bad because that would mean any time setPets gets called it'd re-run render and all the hooks again. See a problem there? It'd run infinitely since requestPets calls setPets. 44 | - You can instead provide which hooks to watch for changes for. In our case, we actually only want it to run once, on creation of the component, and then to not run that effect again. (we'll do searching later via clicking the submit button) You can accomplish this only-run-on-creation by providing an empty array. 45 | - The `// eslint-disable-line react-hooks/exhaustive-deps` tells eslint to shut up about this one run on this one line. Why? Because eslint tries to help you with you the data dependencies rule by watching for anything that _could_ change. In this case, in theory the function could change but we know it's not important. You'll end up silencing this rule a fair bit. 46 | - At the end, we gather take the pets we got back from the API and create Pet components out of each of them. 47 | 48 | > 🏁 [Click here to see the state of the project up until now: 05-useeffect][step] 49 | 50 | [step]: https://github.com/btholt/citr-v6-project/tree/master/05-useeffect 51 | -------------------------------------------------------------------------------- /lessons/04-core-react-concepts/D-custom-hooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "You can even make your own hooks! Brian shows how to extract logic out of a component to share a hook across components!" 3 | --- 4 | 5 | For now, we're going to make a custom hook of our own. Just like `useState` is a hook, there are a few others like `useEffect` (which we'll use in this lesson), `useReducer` (for doing Redux-like reducers), `useRefs` (for when you need to have programmatic access to a DOM node), and `useContext` (for using React's context which we'll do shortly as well.) But like React hooks, we can use these hooks to make our re-usable hooks. 6 | 7 | We need a list of breeds based on which animal is selected. In general this would be nice to request _once_ and if a user returns later to the same animal, that we would have some cache of that. We could implement in the component (and in general I probably would, this is overengineering it for just one use) but let's make a custom hook for it. 8 | 9 | Make a new file called `useBreedList.js` in src and put this in it. 10 | 11 | ```javascript 12 | import { useState, useEffect } from "react"; 13 | 14 | const localCache = {}; 15 | 16 | export default function useBreedList(animal) { 17 | const [breedList, setBreedList] = useState([]); 18 | const [status, setStatus] = useState("unloaded"); 19 | 20 | useEffect(() => { 21 | if (!animal) { 22 | setBreedList([]); 23 | } else if (localCache[animal]) { 24 | setBreedList(localCache[animal]); 25 | } else { 26 | requestBreedList(); 27 | } 28 | 29 | async function requestBreedList() { 30 | setBreedList([]); 31 | setStatus("loading"); 32 | const res = await fetch( 33 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}` 34 | ); 35 | const json = await res.json(); 36 | localCache[animal] = json.breeds || []; 37 | setBreedList(localCache[animal]); 38 | setStatus("loaded"); 39 | } 40 | }, [animal]); 41 | 42 | return [breedList, status]; 43 | } 44 | ``` 45 | 46 | - We're using hooks inside of our custom hook. I can't think of a custom hook you would make that wouldn't make use of other hooks. 47 | - We're returning two things back to the consumer of this custom hook: a list of breeds (including an empty list when it doesn't have anything in it) and an enumerated type of the status of the hook: unloaded, loading, or loaded. We won't be using the enum today but this is how I'd design it later if I wanted to throw up a nice loading graphic while breeds were being loaded. 48 | 49 | Head over to SearchParam.js and put this in there. 50 | 51 | ```javascript 52 | import useBreedList from "./useBreedList"; 53 | 54 | // replace `const breeds = [];` 55 | const [breeds] = useBreedList(animal); 56 | ``` 57 | 58 | That should be enough! Now you should have breeds being populated everything you change animal! (Do note we haven't implemented the submit button yet though.) 59 | 60 | > 🏁 [Click here to see the state of the project up until now: 06-custom-hooks][step] 61 | 62 | [step]: https://github.com/btholt/citr-v6-project/tree/master/06-custom-hooks 63 | -------------------------------------------------------------------------------- /lessons/04-core-react-concepts/E-handling-user-input.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Brian shows how to wire up your app to work with users interacting with the site." 3 | --- 4 | 5 | We've seen one way to handle async code in React: with effects. This is most useful when you need to be reactive to your data changing or when you're setting up or tearing down a component. Sometimes you just need to respond to someone pressing a button. This isn't hard to accomplish either. Let's make it so whenever someone either hits enter or clicks the button it searches for animals. We can do this by listening for submit events on the form. Let's go do that now. In SearchParams.js: 6 | 7 | ```javascript 8 | // inside render 9 | const [pets, setPets] = useState([]); 10 | 11 | // replace
12 | { 14 | e.preventDefault(); 15 | requestPets(); 16 | }} 17 | > 18 | ``` 19 | 20 | Now you should be able to see the network request go out whenever you submit the form. 21 | 22 | This course isn't going into all the ways of handling user interactions in JavaScript. You can register handlers for things mouse leave, mouse enter, key up, key down, and can even handle stuff like copy and paste events, focus, blur, etc. [Here's a list of them from the React docs][docs]. 23 | 24 | [docs]: https://reactjs.org/docs/events.html#supported-events 25 | -------------------------------------------------------------------------------- /lessons/04-core-react-concepts/F-component-composition.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "One component should do one thing. Brian shows you how to break down bigger components into smaller components." 3 | --- 4 | 5 | Our SearchParams component is getting pretty big and doing a lot of heavy lifting. This is against the React way: in general we want small-ish (use your best judgment but lean towards smaller when you have a choice) components that do one thing. When we start having a ballooning component like we do here, take your larger component and break it down into smaller components. 6 | 7 | In general I find two reasons to break a component into smaller components: reusability and organization. When you want to use the same component in multiple places (e.g. a button, a tool tip, etc.) then it's helpful to have one component to maintain, test, use, etc. 8 | 9 | Other times it can be useful to break concepts down into smaller concepts to make a component read better. For example, if we put all the logic for this entire page into one component, it would become pretty hard to read and manage. By breaking it down we can make each component easier to understand when you read it and thus maintain. 10 | 11 | Let's make a better display for our Pets components. Make a new file called Results.js. 12 | 13 | ```javascript 14 | import Pet from "./Pet"; 15 | 16 | const Results = ({ pets }) => { 17 | return ( 18 |
19 | {!pets.length ? ( 20 |

No Pets Found

21 | ) : ( 22 | pets.map((pet) => { 23 | return ( 24 | 33 | ); 34 | }) 35 | )} 36 |
37 | ); 38 | }; 39 | 40 | export default Results; 41 | ``` 42 | 43 | Now go back to SearchParams.js and put this: 44 | 45 | ```javascript 46 | // at top 47 | import Results from "./Results"; 48 | 49 | // under
, still inside the div 50 | ; 51 | ``` 52 | 53 | Now you should be able to make request and see those propagated to the DOM! Pretty great! 54 | 55 | Let's go make Pet.js look decent: 56 | 57 | ```javascript 58 | const Pet = (props) => { 59 | const { name, animal, breed, images, location, id } = props; 60 | 61 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg"; 62 | if (images.length) { 63 | hero = images[0]; 64 | } 65 | 66 | return ( 67 | 68 |
69 | {name} 70 |
71 |
72 |

{name}

73 |

{`${animal} — ${breed} — ${location}`}

74 |
75 |
76 | ); 77 | }; 78 | 79 | export default Pet; 80 | ``` 81 | 82 | Looks much better! The links don't go anywhere yet but we'll get there. We don't have a good loading experience yet though. Right now we just seem unresponsive. Using a new tool to React called Suspense we can make the DOM rendering wait until we finish loading our data, show a loader, and then once it finishes we can resume rendering it. This is coming soon; for now you would just keep track of a loading Boolean and then conditionally show your component or a loading spinner based on whether it was finished loading or not. 83 | 84 | > 🏁 [Click here to see the state of the project up until now: 07-component-composition][step] 85 | 86 | [step]: https://github.com/btholt/citr-v6-project/tree/master/07-component-composition 87 | -------------------------------------------------------------------------------- /lessons/04-core-react-concepts/G-react-dev-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "An essential tool in any React developer's toolbox is the official React Dev Tools extension. Brian shows you how to install and use them." 3 | --- 4 | 5 | React has some really great tools to enhance your developer experience. We'll go over a few of them here. 6 | 7 | ## `NODE_ENV=development` 8 | 9 | React already has a lot of developer conveniences built into it out of the box. What's better is that they automatically strip it out when you compile your code for production. 10 | 11 | So how do you get the debugging conveniences then? Well, if you're using Parcel.js, it will compile your development server with an environment variable of `NODE_ENV=development` and then when you run `parcel build ` it will automatically change that to `NODE_ENV=production` which is how all the extra weight gets stripped out. 12 | 13 | Why is it important that we strip the debug stuff out? The dev bundle of React is quite a bit bigger and quite a bit slower than the production build. Make sure you're compiling with the correct environmental variables or your users will suffer. 14 | 15 | ## Strict Mode 16 | 17 | React has a new strict mode. If you wrap your app in `` it will give you additional warnings about things you shouldn't be doing. I'm not teaching you anything that would trip warnings from `React.StrictMode` but it's good to keep your team in line and not using legacy features or things that will be soon be deprecated. 18 | 19 | Go to App.js and wrap `` in the render call in ``. 20 | 21 | ```javascript 22 | // import at top 23 | import { StrictMode } from "react"; 24 | 25 | // replace render 26 | render( 27 | 28 | 29 | , 30 | document.getElementById("root") 31 | ); 32 | ``` 33 | 34 | ## Dev Tools 35 | 36 | React has wonderful dev tools that the core team maintains. They're available for both Chromium-based browsers and Firefox. They let you do several things like explore your React app like a DOM tree, modify state and props on the fly to test things out, tease out performance problems, and programtically manipulate components. Definitely worth downloading now. 37 | -------------------------------------------------------------------------------- /lessons/04-core-react-concepts/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "book" 3 | } -------------------------------------------------------------------------------- /lessons/05-react-capabilities/A-react-router.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "One component should do one thing. Brian shows you how to break down bigger components into smaller components." 3 | --- 4 | 5 | > In previous versions of this course, I've taught various versions of [React Router][rr] as well as [Reach Router][reach]. It's all written by the same folks (same great people behind [Remix][remix]) but suffice to say it's a bit of a moving target. It's great software and you'll be well served by any of them. This course uses React Router v5 but aware React Router v6 is coming soon or maybe even be out by the time you read this. Reach Router is being folded back into React Router. 6 | 7 | React Router is by far the most popular client side router in the React community. It is mature, being used by big companies, and battle tested at large scales. It also has a lot of really cool capabilities, some of which we'll examine here. 8 | 9 | What we want to do now is to add a second page to our application: a Details page where you can out more about each animal. 10 | 11 | Let's quickly make a second page so we can switch between the two. Make file called Details.js. 12 | 13 | ```javascript 14 | const Details = () => { 15 | return

hi!

; 16 | }; 17 | 18 | export default Details; 19 | ``` 20 | 21 | Now the Results page is its own component. This makes it easy to bring in the router to be able to switch pages. Run `npm install react-router-dom@5.2.0`. 22 | 23 | Now we have two pages and the router available. Let's go make it ready to switch between the two. In `App.js`: 24 | 25 | ```javascript 26 | // at top 27 | import { BrowserRouter as Router, Route } from "react-router-dom"; 28 | import Details from "./Details"; 29 | 30 | // replace 31 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | ; 39 | ``` 40 | 41 | Now we have the router working (but still have an issue)! Try navigating to http://localhost:1234/ and then to http://localhost:1234/details/1. Both should work … sort of! 42 | 43 | - React Router has a ton of features that we're not going to explain here. The docs do a great job. 44 | - The `:id` part is a variable. In `http://localhost:1234/details/1`, `1` would be the variable. 45 | - The killer feature of React Router is that it's really accessible. It manages things like focus so you don't have to. Pretty great. 46 | 47 | On the Details page, notice that both pages render. It has to do with how React Router does routes. 48 | 49 | - React Router will render all components that the path match. 50 | - React Router does partial matches. The URL `/teachers/jem/young` will match the paths `/`, `/teachers`, `/teachers/jem` and `/teachers/jem/young`. It will not match `/young`, `/jem/young`, or `/teachers/young`. 51 | 52 | So let's make it match only one path with a component called Switch. 53 | 54 | ```javascript 55 | // replace react-router-dom import 56 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 57 | 58 | // replace jsx 59 |
60 | 61 |

Adopt Me!

62 | 63 | 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 |
; 72 | ``` 73 | 74 | Now notice it only renders one page at a time. 75 | 76 | So now let's make the two pages link to each other. Go to Pet.js. 77 | 78 | ```javascript 79 | // at top 80 | import { Link } from "react-router-dom"; 81 | 82 | // change wrapping 83 | 84 | […] 85 | ; 86 | ``` 87 | 88 | Why did we change this? Didn't the `` work? It did but with a flaw: every link you clicked would end up in the browser navigating to a whole new page which means React would totally reload your entire app all over again. With `` it can intercept this and just handle that all client-side. Much faster and a better user experience. 89 | 90 | Now each result is a link to a details page! And that id is being passed as a prop to Details. Try replacing the return of Details with: 91 | 92 | ```javascript 93 | import { useParams } from "react-router-dom"; 94 | 95 | const Details = () => { 96 | const { id } = useParams(); 97 | return

{id}

; 98 | }; 99 | 100 | export default Details; 101 | ``` 102 | 103 | The `useParams` hook is how you get params from React Router. It used to be through the props but now they prefer this API. 104 | 105 | Let's make the Adopt Me! header clickable too. 106 | 107 | ```javascript 108 | // import Link too 109 | import { Router, Link } from "react-router-dom"; 110 | 111 | // replace h1 112 |
113 | Adopt Me! 114 |
; 115 | ``` 116 | 117 | Now if you click the header, it'll take you back to the Results page. Cool. Now let's round out the Details page. 118 | 119 | > 🏁 [Click here to see the state of the project up until now: 08-react-router][step] 120 | 121 | [rr]: https://reacttraining.com/react-router/ 122 | [reach]: https://reach.tech/router/ 123 | [rf]: https://twitter.com/ryanflorence 124 | [step]: https://github.com/btholt/citr-v6-project/tree/master/08-react-router 125 | [remix]: https://remix.run 126 | -------------------------------------------------------------------------------- /lessons/05-react-capabilities/B-class-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "While many components are written with hooks, the older API of class-based components are still around and still useful. Brian shows you when and how to use the class components API." 3 | --- 4 | 5 | This class has been showing you the latest APIs for React: hooks. Going forward, these sorts of components will be the default way of writing React. However, the class API still has its uses and isn't going anywhere anytime soon. In this section we're going to go through and learn the basics of it since there's still a lot class code out in the wild and the new API can't do _everything_ the old one can, so it's still useful in some cases. 6 | 7 | Let's go make Details.js as a class. 8 | 9 | ```javascript 10 | // replace Details.js 11 | import { Component } from "react"; 12 | import { withRouter } from "react-router-dom"; 13 | 14 | class Details extends Component { 15 | constructor() { 16 | super(); 17 | this.state = { loading: true }; 18 | } 19 | 20 | async componentDidMount() { 21 | const res = await fetch( 22 | `http://pets-v2.dev-apis.com/pets?id=${this.props.match.params.id}` 23 | ); 24 | const json = await res.json(); 25 | this.setState(Object.assign({ loading: false }, json.pets[0])); 26 | } 27 | 28 | render() { 29 | console.log(this.state); 30 | 31 | if (this.state.loading) { 32 | return

loading …

; 33 | } 34 | 35 | const { animal, breed, city, state, description, name } = this.state; 36 | 37 | return ( 38 |
39 |
40 |

{name}

41 |

{`${animal} — ${breed} — ${city}, ${state}`}

42 | 43 |

{description}

44 |
45 |
46 | ); 47 | } 48 | } 49 | 50 | export default withRouter(Details); 51 | ``` 52 | 53 | - Every class component extends `React.Component`. Every class component must have a render method that returns some sort of JSX / markup / call to `React.createElement`. 54 | - Not every component needs to have a constructor. Many don't. I'll show you momentarily how you nearly never need to have one. In this case we need it to instantiate the state object (which we'll use instead of `useState`.) If you have a constructor, you _have_ to do the `super(props)` to make sure that the props are passed up to React so React can keep track of them. 55 | - `componentDidMount` is a function that's called after the first rendering is completed. This pretty similar to a `useEffect` call that only calls the first time. This is typically where you want to do data fetching. It doesn't have to be async; we just made it async here to make the data fetching easy. 56 | - Notice instead of getting props via parameters and state via `useState` we're getting it from the instance variables `this.state` and `this.props`. This is how it works with class components. Neither one will you mutate directly. 57 | - `this.state` is the mutable state of the component (like useState). You'll use `this.setState` to mutate it (don't modify it directly.) 58 | - `this.props` comes from the parent component, similar to parameter given to the render functions that we pull props out of. 59 | - `withRouter()` is called a higher order component and is a bit of an advance concept. Basically we're composing functionality into our component via react-router. Think of `useParams`: it mixes in functionality from react-router by calling a hook. This is how you get that custom hook behavior of mixing in library functionality with class components. Redux does this too, but otherwise it's not overly common. 60 | 61 | ## Other lifecycle methods 62 | 63 | This class doesn't cover all the lifecycle methods but you can imagine having different timings for different capabilities of a component can be useful. For example, if you have a set of props that come in and you need to filter those props before you display them, you can use `getDerivedStateFromProps`. Or if you need to react to your component being removed from the DOM (like if you're subscribing to an API and you need to dispose of the subscription) you can use `componentWillUnmount`. 64 | 65 | There are lots more you can check out in [the React docs here][docs]. 66 | 67 | [docs]: https://reactjs.org/docs/react-component.html 68 | -------------------------------------------------------------------------------- /lessons/05-react-capabilities/C-class-properties.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Using constructors to set initial state for class components is verbose and can be done better. Brian teaches you to use the new feature in JavaScript, class properties, to make your code easy to read." 3 | --- 4 | 5 | The constructor is annoying. We can use something called class properties to make it a lot nicer and easier to read. Class properties are a new part of JavaScript so we need Parcel transform the code when Parcel transpiles our code. Luckily our config will do that by itself so no further changes are needed (previously we did need to.) 6 | 7 | Since we're going to take ahold of our own Babel configuration, we need to take over _all of it_. Parcel won't do it for us anymore. So install the following: 8 | 9 | ```bash 10 | npm i -D @babel/plugin-proposal-class-properties@7.13.0 @babel/preset-env@7.13.5 @babel/eslint-parser@7.13.4 11 | ``` 12 | 13 | Now modify your `.babelrc` with the following: 14 | 15 | ```json 16 | { 17 | "presets": [ 18 | [ 19 | "@babel/preset-react", 20 | { 21 | "runtime": "automatic" 22 | } 23 | ], 24 | "@babel/preset-env" 25 | ], 26 | "plugins": ["@babel/plugin-proposal-class-properties"] 27 | } 28 | ``` 29 | 30 | Babel's core concept is a plugin. Every one sort of a transformation it can perform is encapsulated into a plugin. Here we're including one explicitly: transform-class-properties. Then we're including a _preset_ as well. A preset is just a group of plugins, grouped together for convenience. `env` is a particularly good one you should expect to normally use. 31 | This will allow us too to make ESLint play nice too (Prettier handles this automatically.) Add one line to the top level of your `.eslintrc.json`: 32 | 33 | ```json 34 | { 35 | … 36 | "parser": "@babel/eslint-parser", 37 | … 38 | } 39 | ``` 40 | 41 | Now with this, we can modify Details to be as so: 42 | 43 | ```javascript 44 | // replace constructor 45 | state = { loading: true }; 46 | ``` 47 | 48 | Loads easier to read, right? 49 | -------------------------------------------------------------------------------- /lessons/05-react-capabilities/D-managing-state-in-class-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Class components work a little different from hooks in terms of marshalling state. Brian teaches you how to manage your state using setState and life cycle methods." 3 | --- 4 | 5 | Okay, so on this page, notice first we have a loading indicator (this one isn't nice looking but you could put some effort into it if you wanted.) This is a good idea while you're waiting for data to load. 6 | 7 | Let's make a nice photo carousel of the pictures for the animal now. Make a new file called Carousel.js: 8 | 9 | ```javascript 10 | import { Component } from "react"; 11 | 12 | class Carousel extends Component { 13 | state = { 14 | active: 0, 15 | }; 16 | 17 | static defaultProps = { 18 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"], 19 | }; 20 | 21 | render() { 22 | const { active } = this.state; 23 | const { images } = this.props; 24 | return ( 25 |
26 | animal 27 |
28 | {images.map((photo, index) => ( 29 | // eslint-disable-next-line 30 | animal thumbnail 36 | ))} 37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | export default Carousel; 44 | ``` 45 | 46 | Add the Carousel component to the Detail page. 47 | 48 | ```javascript 49 | // import at top 50 | import Carousel from "./Carousel"; 51 | 52 | // at top of Details function 53 | const { animal, breed, city, state, description, name, images } = this.state; 54 | 55 | // first component inside div.details 56 | ; 57 | ``` 58 | 59 | - defaultProps does exactly what it sounds like: it allows you to set props that a component has by default if they're not furnished by parent. That way we don't have to do detection logic and can just assume they're always there. 60 | 61 | Let's make it so we can react to someone changing the photo on the carousel. 62 | 63 | ```javascript 64 | // add event listener 65 | handleIndexClick = event => { 66 | this.setState({ 67 | active: +event.target.dataset.index 68 | }); 69 | }; 70 | 71 | // above img 72 | // eslint-disable-next-line 73 | 74 | // add to img 75 | onClick={this.handleIndexClick} 76 | data-index={index} 77 | ``` 78 | 79 | - This is how you handle events in React class components. If it was keyboard handler, you'd do an onChange or onKeyUp, etc. handler. 80 | - Notice that the handleIndexClick function is an arrow function. This is because we need the `this` in `handleIndexClick` to be the correct `this`. An arrow function assures that because it will be the scope of where it was defined. This is common with how to deal with event handlers with class components. 81 | - The data attribute comes back as a string. We want it to be a number, hence the `+`. 82 | - We're doing bad accessibility stuff. But this makes it a lot simpler for learning for now. But don't do this in production. 83 | 84 | > 🏁 [Click here to see the state of the project up until now: 09-managing-state-in-class-components][step] 85 | 86 | [step]: https://github.com/btholt/citr-v6-project/tree/master/09-managing-state-in-class-components 87 | [babel]: https://babeljs.io/ 88 | -------------------------------------------------------------------------------- /lessons/05-react-capabilities/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "map" 3 | } -------------------------------------------------------------------------------- /lessons/06-special-case-react-tools/A-error-boundaries.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "" 3 | --- 4 | 5 | Frequently there's errors with APIs with malformatted or otherwise weird data. Let's be defensive about this because we still want to use this API but we can't control when we get errors. We're going to use a feature called `componentDidCatch` to handle this. This is something you can't do with hooks so if you needed this sort of functionality you'd have to use a class component. 6 | 7 | This will also catch 404s on our API if someone give it an invalid ID! 8 | 9 | A component can only catch errors in its children, so that's important to keep in mind. It cannot catch its own errors. Let's go make a wrapper to use on Details.js. Make a new file called ErrorBoundary.js 10 | 11 | ```javascript 12 | // mostly code from reactjs.org/docs/error-boundaries.html 13 | import { Component } from "react"; 14 | import { Link } from "react-router-dom"; 15 | 16 | class ErrorBoundary extends Component { 17 | state = { hasError: false }; 18 | static getDerivedStateFromError() { 19 | return { hasError: true }; 20 | } 21 | componentDidCatch(error, info) { 22 | console.error("ErrorBoundary caught an error", error, info); 23 | } 24 | render() { 25 | if (this.state.hasError) { 26 | return ( 27 |

28 | There was an error with this listing. Click here{" "} 29 | to back to the home page or wait five seconds. 30 |

31 | ); 32 | } 33 | 34 | return this.props.children; 35 | } 36 | } 37 | 38 | export default ErrorBoundary; 39 | ``` 40 | 41 | - Now anything that is a child of this component will have errors caught here. Think of this like a catch block from try/catch. 42 | - A static method is one that can be called on the constructor. You'd call this method like this: `ErrorBoundary.getDerivedStateFromError(error)`. This method must be static. 43 | - If you want to call an error logging service, `componentDidCatch` would be an amazing place to do that. I can recommend [Azure Monitor][azure], [Sentry][sentry], and [TrackJS][trackjs]. 44 | 45 | Let's go make Details use it. Go to Details.js 46 | 47 | ```javascript 48 | // add import 49 | import ErrorBoundary from "./ErrorBoundary"; 50 | 51 | // replace export 52 | const DetailsWithRouter = withRouter(Details); 53 | 54 | export default function DetailsErrorBoundary(props) { 55 | return ( 56 | 57 | 58 | 59 | ); 60 | } 61 | ``` 62 | 63 | - Now this is totally self contained. No one rendering Details has to know that it has its own error boundary. I'll let you decide if you like this pattern or if you would have preferred doing this in App.js at the Router level. Differing opinions exist. 64 | - We totally could have made ErrorBoundary a bit more flexible and made it able to accept a component to display in cases of errors. In general I recommend the "WET" code rule (as opposed to [DRY][dry], lol): Write Everything Twice (or I even prefer Write Everything Thrice). In this case, we have one use case for this component, so I won't spend the extra time to make it flexible. If I used it again, I'd make it work for both of those use cases, but not _every_ use case. On the third or fourth time, I'd then go back and invest the time to make it flexible. 65 | 66 | Let's make it redirect automatically after five seconds. We could do a set timeout in the `componentDidCatch` but let's do it with `componentDidUpdate` to show you how that works. 67 | 68 | ```javascript 69 | // top 70 | import { Link, Redirect } from "react-router-dom"; 71 | 72 | // add redirect 73 | state = { hasError: false, redirect: false }; 74 | 75 | // under componentDidCatch 76 | componentDidUpdate() { 77 | if (this.state.hasError) { 78 | setTimeout(() => this.setState({ redirect: true }), 5000); 79 | } 80 | } 81 | 82 | // first thing inside render 83 | if (this.state.redirect) { 84 | return ; 85 | } } else if (this.state.hasError) { 86 | … 87 | } 88 | ``` 89 | 90 | - `componentDidUpdate` is how you react to state and prop changes with class components. In this case we're reacting to the state changing. You're also passed the previous state and props in the paremeters (which we didn't need) in case you want to detect what changed. 91 | - Rendering Redirect components is how you do redirects with React Router. You can also do it progamatically but I find this approach elegant. 92 | 93 | > 🏁 [Click here to see the state of the project up until now: 10-error-boundaries][step] 94 | 95 | [step]: https://github.com/btholt/citr-v6-project/tree/master/10-error-boundaries 96 | [azure]: https://azure.microsoft.com/en-us/services/monitor/?WT.mc_id=reactintro-github-brholt 97 | [sentry]: https://sentry.io/ 98 | [trackjs]: https://trackjs.com/ 99 | [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself 100 | -------------------------------------------------------------------------------- /lessons/06-special-case-react-tools/B-context.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "" 3 | --- 4 | 5 | Historically I have not taught context when teaching React. This was for a couple reasons. First of all, the API they were using was still unofficial, however they standardized it in version 16. Secondly, normally you don't need context; React's state is enough. Thirdly, the old API was bad, in my opinion. The new one is pretty good. 6 | 7 | So here we go. What is context? Context is like state, but instead of being confined to a component, it's global to your application. It's application-level state. This is dangerous. Avoid using context until you _have_ to use it. One of React's primary benefit is it makes the flow of data obvious by being explicit. This can make it cumbersome at times but it's worth it because your code stays legible and understandable. Things like context obscure it. 8 | 9 | Context (mostly) replaces Redux. Well, typically. It fills the same need as Redux. I really can't see why you would need to use both. Use one or the other. 10 | 11 | Again, this is a contrived example. What we're doing here is overkill and should be accomplished via React's normal patterns. A better example would be something like a user's logged-in information. But let's check out what this looks like with theme. 12 | 13 | Imagine if we wanted to let the user choose a simple theme for the site. And we want to make that theme stick as the user navigates across different pages. This means the state has to live outside of the route where it's selected. We could use Redux for this, we could use React itself, or we're going to use context, to teach you what that looks like. 14 | 15 | Make a new file called ThemeContext.js: 16 | 17 | ```javascript 18 | import { createContext } from "react"; 19 | 20 | const ThemeContext = createContext(["green", () => {}]); 21 | 22 | export default ThemeContext; 23 | ``` 24 | 25 | `createContext` is a function that returns an object with two React components in it: a Provider and a Consumer. A Provider is how you scope where a context goes. A context will only be available inside of the Provider. You only need to do this once. 26 | 27 | A Consumer is how you consume from the above provider. A Consumer accepts a function as a child and gives it the context which you can use. We won't be using the Consumer directly: a function called `useContext` will do that for us. 28 | 29 | The object provided to context is the default state it uses when it can find no Provider above it, useful if there's a chance no provider will be there and for testing. It's also useful for TypeScript because TypeScript will enforce these types. Here we're giving it the shape of a `useState` call because we'll using `useState` with it. You do not have to use context with hooks; [see v4][v4] if you want to see how to do it without hooks. That example also has a more complicated data shape. This example is a lot more simple. If you wanted a more complicated data shape, you'd replace `"green"` with an object full of other properties. 30 | 31 | Now we're going to make all the buttons' background color in the app be governed by the theme. First let's go to App.js: 32 | 33 | ```javascript 34 | // import useState and ThemeContext 35 | import { StrictMode, useState } from "react"; 36 | import ThemeContext from "./ThemeContext"; 37 | 38 | // top of App function body 39 | const theme = useState("darkblue"); 40 | 41 | // wrap the rest of the app 42 | […]; 43 | ``` 44 | 45 | - We're going to use the `useState` hook because theme is actually going to be kept track of like any other piece of state: it's not any different. You can think of context like a wormhole: whatever you chuck in one side of the wormhole is going to come out the other side. 46 | - You have to wrap your app in a `Provider`. This is the mechanism by which React will notify the higher components to re-render whenever our context changes. Then whatever you pass into the value prop (we passed in the complete hook, the value and updater pair) will exit on the other side whenever we ask for it. 47 | - Note that the theme will only be available _inside_ of this provider. So if we only wrapped the `
` route with the Provider, that context would not be available inside of ``. 48 | - Side note: if your context is _read only_ (meaning it will _never change_) you actually can skip wrapping your app in a Provider. 49 | 50 | Next let's go to `SearchParams.js`: 51 | 52 | ```javascript 53 | // import at top 54 | import React, { useState, useEffect, useContext } from "react"; 55 | import ThemeContext from "./ThemeContext"; 56 | 57 | // top of SearchParams function body 58 | const [theme] = useContext(ThemeContext); 59 | 60 | // replace button 61 | ; 62 | ``` 63 | 64 | - Now your button should be a beautiful shade of `darkblue`. 65 | - `useContext` is how you get the context data out of a given context (you can lots of various types of context in a given app.) 66 | - Right now it's just reading from it and a pretty silly use of context. But let's go make Details.js use it as well. 67 | 68 | Let's go do this in Details.js 69 | 70 | ```javascript 71 | // import 72 | import ThemeContext from "./ThemeContext"; 73 | 74 | // replace button 75 | 76 | {([theme]) => ( 77 | 78 | )} 79 | ; 80 | ``` 81 | 82 | - This is how you use context inside of a class component. 83 | - Remember you cannot use hooks inside class components at all. This is why we're using the `Consumer` from `ThemeContext`. Functionally this works the same way though. 84 | 85 | Lastly let's go make the theme changeable. Head back to SearchParams.js. 86 | 87 | ```javascript 88 | // also grab setTheme 89 | const [theme, setTheme] = useContext(ThemeContext); 90 | 91 | // below BreedDropdown 92 | ; 105 | ``` 106 | 107 | - This looks relatively similar to hooks, right? It should because it works the same! 108 | - Now try changing the theme and navigating to a pet page. Notice the theme is consistent! The theme is kept between pages because it's kept at the App level and App is never unmounted so its state persists between route changes. 109 | - You can have multiple layers of context. If I wrapped SearchParams in its own `Provider` (in addition to the one that already exists), the SearchParams context would read from that because it's the closest one whereas Details would read from the top level one because it's the only one. 110 | 111 | That's it for context! Something like theming would be perfect for context. It's for app-level data. Everything else should be boring-ol' state. 112 | 113 | > 🏁 [Click here to see the state of the project up until now: 11-context][step] 114 | 115 | [step]: https://github.com/btholt/citr-v6-project/tree/master/11-context 116 | [v4]: https://btholt.github.io/complete-intro-to-react-v4/context 117 | -------------------------------------------------------------------------------- /lessons/06-special-case-react-tools/C-portals-and-refs.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "" 3 | --- 4 | 5 | Another nice feature React is something called a Portal. You can think of the portal as a separate mount point (the actual DOM node which your app is put into) for your React app. A common use case for this is going to be doing modals. You'll have your normal app with its normal mount point and then you can also put different content into a separate mount point (like a modal or a contextual nav bar) directly from a component. Pretty cool! 6 | 7 | First thing, let's go into index.html and add a separate mount point: 8 | 9 | ```html 10 | 11 | 12 | ``` 13 | 14 | This where the modal will actually be mounted whenever we render to this portal. Totally separate from our app root. 15 | 16 | Next create a file called Modal.js: 17 | 18 | ```javascript 19 | import React, { useEffect, useRef } from "react"; 20 | import { createPortal } from "react-dom"; 21 | 22 | const modalRoot = document.getElementById("modal"); 23 | 24 | const Modal = ({ children }) => { 25 | const elRef = useRef(null); 26 | if (!elRef.current) { 27 | elRef.current = document.createElement("div"); 28 | } 29 | 30 | useEffect(() => { 31 | modalRoot.appendChild(elRef.current); 32 | return () => modalRoot.removeChild(elRef.current); 33 | }, []); 34 | 35 | return createPortal(
{children}
, elRef.current); 36 | }; 37 | 38 | export default Modal; 39 | ``` 40 | 41 | - This will mount a div and mount inside of the portal whenever the Modal is rendered and then _remove_ itself whenever it's unrendered. 42 | - We're using the feature of `useEffect` that if you need to clean up after you're done (we need to remove the div once the Modal is no longer being rendered) you can return a function inside of `useEffect` that cleans up. 43 | - We're also using a ref here via the hook `useRef`. Refs are like instance variables for function components. Whereas on a class you'd say `this.myVar` to refer to an instance variable, with function components you can use refs. They're containers of state that live outside a function's closure state which means anytime I refer to `elRef.current`, it's **always referring to the same element**. This is different from a `useState` call because the variable returned from that `useState` call will **always refer to the state of the variable when that function was called.** It seems like a weird hair to split but it's important when you have async calls and effects because that variable can change and nearly always you want the `useState` variable, but with something like a portal it's important we always refer to the same DOM div; we don't want a lot of portals. 44 | - Down at the bottom we use React's `createPortal` to pass the children (whatever you put inside ``) to the portal div. 45 | 46 | Now go to Details.js and add: 47 | 48 | ```javascript 49 | // at the top 50 | import Modal from "./Modal"; 51 | 52 | // add showModal 53 | state = { loading: true, showModal: false }; 54 | 55 | // above render 56 | toggleModal = () => this.setState({ showModal: !this.state.showModal }); 57 | adopt = () => (window.location = "http://bit.ly/pet-adopt"); 58 | 59 | // add showModal 60 | const { animal, breed, city, state, description, name, images, showModal } = 61 | this.state; 62 | 63 | // add onClick to ; 67 | 68 | // below description 69 | { 70 | showModal ? ( 71 | 72 |
73 |

Would you like to adopt {name}?

74 |
75 | 76 | 77 |
78 |
79 |
80 | ) : null; 81 | } 82 | ``` 83 | 84 | - We're using a simple `window.location` redirect since we're heading off site. This is bad accessibility so you should be extra cautious when doing this. The button should be an `
` tag but I wanted to show you how to do it. But now if you click Yes on the adopt modal it'll take you to the page when you actually can adopt a pet! 85 | - Notice that despite we're rendering a whole different part of the DOM we're still referencing the state in Details.js. This is the magic of Portals. You can use state but render in different parts of the DOM. Imagine a sidebar with contextual navigation. Or a contextual footer. It opens up a lot of cool possibilities. 86 | 87 | That's it! That's how you make a modal using a portal in React. This used to be significantly more difficult to do but with portals it became trivial. The nice thing about portals is that despite the actual elements being in different DOM trees, these are in the same React trees, so you can do event bubbling up from the modal. Some times this is useful if you want to make your Modal more flexible (like we did.) 88 | 89 | > 🏁 [Click here to see the state of the project up until now: 12-portals-and-refs][step] 90 | 91 | [portal]: https://reactjs.org/docs/portals.html 92 | [step]: https://github.com/btholt/citr-v6-project/tree/master/12-portals-and-refs 93 | -------------------------------------------------------------------------------- /lessons/06-special-case-react-tools/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "bolt" 3 | } -------------------------------------------------------------------------------- /lessons/07-end-of-intro/A-conclusion.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "A quick note and congratulations from Brian for completing the Complete Intro to React v6!" 3 | --- 4 | 5 | This concludes our intro to React! You know now nearly every feature of the React core library (there are a few more but are rarely needed.) What makes React wonderful is not only the core library itself but the ecosystem around. Indeed, the ecosystem around it is as much a reason to learn React as React itself. 6 | 7 | The modules following this one are considered to be optional modules: you don't need to know all of these; you can just pick the ones you need for your project or what interests you the most and leave the others behind. For the most part these modules will be self-contained: you don't need to complete all the optional modules to understand what's going on. Some _will_ use the code you built in the previous modules here but feel free to clone the complete project and work from there. 8 | 9 | Good job getting this far and good luck on the next modules! 10 | -------------------------------------------------------------------------------- /lessons/07-end-of-intro/B-ways-to-expand-your-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "If you want to improve this app and have some more practice with React, here are some ideas for you!" 3 | --- 4 | 5 | If you want to improve this app and have some more practice with React, here are some ideas for you! 6 | 7 | ## Take the Intermediate React Course 8 | 9 | Take the Intermediate course! You'll learn great things like Tailwind, how to write tests for React, TypeScript, how to use React with Node.js, code splitting, and a whole slew of other things. 10 | 11 | ## Paginate the Results 12 | 13 | Our home page doesn't paginate doesn't results. With some nice buttons, you could paginate through the various results so a user isn't stuck looking at the top ten results. `http://pets-v2.dev-apis.com/pets?animal=dog&page=1` will give you the second page of dogs (pages for this API start at 0). 14 | 15 | ## Use a Real API 16 | 17 | [Use the Petfinder API!][pf] In previous versions of this course we did actually use the Petfinder API but it was occasionally unreliable so I made the fake API you're using to make sure you could always work through the code okay. 18 | 19 | [They even have a JavaScript library!][pf-sdk] You'll have to sign up for API credentials (secret and key) on their website, install the library, and then use the library everywhere we were using `fetch()` you need to change it to `pf.animal.search()` or whatever calls. This API returns different shpae of data. Last time I checked it looks like this: 20 | 21 | ```json 22 | { 23 | "id": 44895949, 24 | "organization_id": "NOTREAL", 25 | "url": "https://www.url.to.the.animal/", 26 | "type": "Rabbit", 27 | "species": "Rabbit", 28 | "breeds": { 29 | "primary": "Mini Rex", 30 | "secondary": null, 31 | "mixed": true, 32 | "unknown": false 33 | }, 34 | "colors": { 35 | "primary": "Brown / Chocolate", 36 | "secondary": "Tan", 37 | "tertiary": null 38 | }, 39 | "age": "Adult", 40 | "gender": "Female", 41 | "size": "Small", 42 | "coat": "Short", 43 | "attributes": { 44 | "spayed_neutered": true, 45 | "house_trained": false, 46 | "declawed": null, 47 | "special_needs": false, 48 | "shots_current": false 49 | }, 50 | "environment": { 51 | "children": null, 52 | "dogs": null, 53 | "cats": null 54 | }, 55 | "tags": [], 56 | "name": "Betty", 57 | "description": "Hi my name is Betty and I am 1 year old.", 58 | "photos": [ 59 | { 60 | "small": "https://dl5zpyw5k3jeb.cloudfront.net/photos/pets/44895949/1/?bust=1559843027&width=100", 61 | "medium": "https://dl5zpyw5k3jeb.cloudfront.net/photos/pets/44895949/1/?bust=1559843027&width=300", 62 | "large": "https://dl5zpyw5k3jeb.cloudfront.net/photos/pets/44895949/1/?bust=1559843027&width=600", 63 | "full": "https://dl5zpyw5k3jeb.cloudfront.net/photos/pets/44895949/1/?bust=1559843027" 64 | } 65 | ], 66 | "status": "adoptable", 67 | "published_at": "2019-06-06T17:44:29+0000", 68 | "contact": { 69 | "email": "fake@example.com", 70 | "phone": "(555) 555-5555", 71 | "address": { 72 | "address1": "Not Real SPCA", 73 | "address2": "Fake Place", 74 | "city": "Fake City", 75 | "state": "FS", 76 | "postcode": "00000", 77 | "country": "US" 78 | } 79 | }, 80 | "_links": { 81 | "self": { 82 | "href": "/v2/animals/44895949" 83 | }, 84 | "type": { 85 | "href": "/v2/types/rabbit" 86 | }, 87 | "organization": { 88 | "href": "/v2/organizations/NOTREAL" 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | Once you've done all this, your code will actually be populated with real animals!! 95 | 96 | ## Deploy your Code 97 | 98 | You should deploy your code to the cloud and tweet it at me! Great options for places for you to deploy include: 99 | 100 | - [Azure Static Web Apps][swa] (I'm biased, I work here) 101 | - [Google Firebase][gcp] 102 | - [AWS Amplify][aws] 103 | - [Netlify][netlify] 104 | - [Vercel][vercel] 105 | 106 | ## Use the Theme Selector in More Places 107 | 108 | We could do better than just using the theme selector just on buttons. Make a dark mode! Make a party mode! Add animiations! This would be great when paired with the Tailwind section from Intermediate React. 109 | 110 | ## Add a Navigation Bar 111 | 112 | Right now we don't have a great navigation story for our little pet finding app. Add a navigation bar at the top so users can easily navigate our site. 113 | 114 | ## Play with other tools 115 | 116 | I showed you how to use Parcel but consider trying one of the newer build systems like [Vite], [Snowpack], [ESBuild], or any of the others. You could also use one of the popular mainstays like [Webpack][webpack] or [Rollup][rollup]. 117 | 118 | ## Let me know 119 | 120 | Please! Let's share all the great apps we make here so we can provide inspirations for others and get some high fives on the cool work we do. 121 | 122 | If you have a cool project you're particularly proud of, open a PR on this repo so that we all can see it! [Community repo][comm]. 123 | 124 | [pf]: https://www.petfinder.com/developers/ 125 | [pf-sdk]: https://github.com/petfinder-com/petfinder-js-sdk 126 | [swa]: https://azure.microsoft.com/en-us/services/app-service/static/ 127 | [gcp]: https://firebase.google.com/ 128 | [aws]: https://aws.amazon.com/amplify/ 129 | [netlify]: https://www.netlify.com/ 130 | [vercel]: https://vercel.com/ 131 | [vite]: https://vitejs.dev/ 132 | [snowpack]: https://www.snowpack.dev/ 133 | [esbuild]: https://esbuild.github.io/ 134 | [webpack]: https://webpack.js.org/ 135 | [rollup]: https://www.rollupjs.org/guide/en/ 136 | [comm]: https://github.com/btholt/citrv6-community 137 | -------------------------------------------------------------------------------- /lessons/07-end-of-intro/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "stopwatch" 3 | } -------------------------------------------------------------------------------- /lessons/08-intermediate-react-v3/A-welcome-to-intermediate-react-v4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Welcome to Intermediate React v4" 3 | description: "Brian introduces you to Intermediate React v4 and explains how the course is structured." 4 | --- 5 | 6 | Welcome to Intermediate React v3 as taught by Brian Holt 7 | 8 | If you haven't yet, read the [intro page][intro] as it explains well who I am, why I'm teaching this course, and what my background is. 9 | 10 | This course is structured a bit different than the Complete Intro to React v6. Whereas the Intro class is a project based class and the whole class builds on one continuous project, this one takes a bunch of unrelated concepts and teaches them as little modules. Feel free to skip modules that don't apply to you. 11 | 12 | All project files are here: [citr-v6-project][citr]. This is where you'll find all the various solutions and steps to different parts of the project. 13 | 14 | Specifically, most modules will work on a fresh copy of [this folder][project]. This folder is the completed project from the Complete Intro to React v6. After each module, we'll restart with a fresh copy of this repo (i.e. the modules **do not** build on each other.) 15 | 16 | Alright! Let's have some fun with Intermdiate React v3! 17 | 18 | [intro]: https://btholt.github.io/complete-intro-to-react-v6/intro 19 | [citr]: https://github.com/btholt/citr-v6-project/ 20 | [project]: https://github.com/btholt/citr-v6-project/tree/master/12-portals-and-refs 21 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/A-usestate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useState" 3 | description: "" 4 | --- 5 | 6 | [Open this CodeSandbox.][codesandbox] All of the examples are in there. We will not be using the code in project for this section, just this CodeSandbox. 7 | 8 | In the preceding course, we went over `useState`, `useEffect`, `useContext`, and `useRef`. These are the most common hooks and likely 99% of what you're going to use. However it's good to know what other tools are in your toolbox for the 1% of problems. We'll go through, example-by-example, and work out what all these hooks can do for you. (we'll review the ones we've talked about already too.) 9 | 10 | [Component][state]. 11 | 12 | `useState` allows us to make our components stateful. Whereas this previously required using a class component, hooks give us the ability to write it using just functions. It allows us to have more flexible components. In our example component, everytime you click on the h1 (bad a11y, by the way) it'll change colors. It's doing this by keeping that bit of state in a hook which is being fed in anew every render so it always has the latest state. 13 | 14 | [codesandbox]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/ 15 | [state]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FState.js 16 | [closures]: https://frontendmasters.com/courses/javascript-foundations/closure-introduction/ 17 | [fibonacci]: https://en.wikipedia.org/wiki/Fibonacci_number 18 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/B-useeffect.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useEffect" 3 | description: "" 4 | --- 5 | 6 | [Component][effect] 7 | 8 | Effects are how you recreate `componentDidMount`, `componentDidUpdate`, and `componentDidUnmount` from React. Inside `useEffect`, you can do any sort of sidd-effect type action that you would have previously done in one of React's lifecycle method. You can do things like fire AJAX requests, integrate with third party libraries (like a jQuery plugin), fire off some telemetry, or anything else that need to happen on the side for your component. 9 | 10 | In our case, we want our component to continually update to show the time so we use setTimeout inside our effect. After the timeout calls the callback, it updates the state. After that render happens, it schedules another effect to happen, hence why it continues to update. You could provide a second parameter of `[]` to `useEffect` (after the function) which would make it only update once. This second array is a list of dependencies: only re-run this effect if one of these parameters changed. In our case, we want to run after every render so we don't give it this second parameter. 11 | 12 | [effect]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FEffect.js 13 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/C-usecontext.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useContext" 3 | description: "" 4 | --- 5 | 6 | [Component][context] 7 | 8 | An early problem with the React problem is called "data tunneling" or "prop drilling". This is when you have a top level component (in our case the parent component) and a child component way down in the hierarchy that need the same data (like the user object.) We could pass that data down, parent-to-child, for each of the intermediary components but that sucks because now each of `LevelTwo`, `LevelThree`, and `LevelFour` all have to know about the user object even when they themselves don't need it, just their children. This is prop drilling: passing down this data in unnecessary intermediaries. 9 | 10 | Enter context. Context allows you to create a wormhole where stuff goes in and a wormhole in a child component where that same data comes out and the stuff in the middle doesn't know it's there. Now that data is available anywhere inside of the `UserContext.Provider`. `useContext` just pulls that data out when given a Context object as a parameter. You don't have to use `useState` and `useContext` together (the data can be any shape, not just `useState`-shaped) but I find it convenient when child components need to be able to update the context as well. 11 | 12 | In general, context adds a decent amount of complexity to an app. A bit of prop drilling is fine. Only put things in context that are truly application-wide state like user information or auth keys and then use local state for the rest. 13 | 14 | Often you'll use context instead of Redux or another state store. You could get fancy and use `useReducer` and `useContext` together to get a pretty great approximation of Redux-like features. 15 | 16 | [context]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FContext.js 17 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/D-useref.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useRef" 3 | description: "" 4 | --- 5 | 6 | [Component][ref] 7 | 8 | Refs are useful for several things, we'll explore two of the main reasons in these examples. I want to show you the first use case: how to emulate instance variables from React. 9 | 10 | In order to understand why refs are useful, you need to understand [how closures work][closures]. In our component, when a user clicks, it sets a timeout to log both the state and the ref's number after a second. One thing to keep in mind that **the state and the ref's number are always the same**. They are never out of lockstep since they're updated at the same time. _However_, since we delay the logging for a second, when it alerts the new values, it will capture what the state was when we first called the timeout (since it's held on to by the closure) but it will always log the current value since that ref is on an object that React consistently gives the same object back to you. Because it's the same object and the number is a property on the object, it will always be up to date and not subject to the closure's scope. 11 | 12 | Why is this useful? It can be useful for things like holding on to `setInterval` and `setTimeout` IDs so they can be cleared later. Or any bit of statefulness that _could_ change but you don't want it to cause a re-render when it does. 13 | 14 | It's also useful for referencing DOM nodes directly and we'll see that a bit later in this section. 15 | 16 | [ref]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FRef.js 17 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/E-usereducer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useReducer" 3 | description: "" 4 | --- 5 | 6 | [Component][reducer] 7 | 8 | I'm going to assume you're familiar with Redux. If not, there's a brief section on it [here](redux-getting-started). `useReducer` allows us to do Redux-style reducers but inside a hook. Here, instead of having a bunch of functions to update our various properties, we have one reducer that handles all the updates based on an action type. This is a preferable approach if you have complex state updates or if you have a situation like this: all of the state updates are very similar so it makes sense to contain all of them in one function. 9 | 10 | [reducer]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FReducer.js 11 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/F-usememo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useMemo" 3 | description: "useMemo memoizes expensive function calls so they only are re-evaluated when needed." 4 | --- 5 | 6 | [Component][memo] 7 | 8 | `useMemo` and `useCallback` are performance optimizations. Use them only when you already have a performance problem instead of pre-emptively. It adds unnecessary complexity otherwise. 9 | 10 | `useMemo` memoizes expensive function calls so they only are re-evaluated when needed. I put in the [fibonacci sequence][fibonacci] in its recursive style to simulate this. All you need to know is that once you're calling `fibonacci` with 30+ it gets quite computationally expensive and not something you want to do unnecessarily as it will cause pauses and jank. It will now only call `fibonacci` if count changes and will just the previous, memoized answer if it hasn't changed. 11 | 12 | If we didn't have the `useMemo` call, everytime I clicked on the title to cause the color to change from red to green or vice versa it'd unnecessarily recalculate the answer of `fibonacci` but because we did use `useMemo` it will only calculate it when `num` has changed. 13 | 14 | Feel try to remove `useMemo`, get `num` to 40 or so, and then click the h1. It'll be slow. 15 | 16 | [memo]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FMemo.js 17 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/G-usecallback.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useCallback" 3 | description: "useCallback is quite similar and indeed it's implemented with the same mechanisms as useMemo except it's a callback instead of a value" 4 | --- 5 | 6 | [Component][callback] 7 | 8 | `useCallback` is quite similar and indeed it's implemented with the same mechanisms as `useMemo`. Our goal is that `ExpensiveComputationComponent` only re-renders whenever it absolutely must. Typically whenever React detects a change higher-up in an app, it re-renders everything underneath it. This normally isn't a big deal because React is quite fast at normal things. However you can run into performance issues sometimes where some components are bad to re-render without reason. 9 | 10 | In this case, we're using a new feature of React called `React.memo`. This is similar to `PureComponent` where a component will do a simple check on its props to see if they've changed and if not it will not re-render this component (or its children, which can bite you.) `React.memo` provides this functionality for function components. Given that, we need to make sure that the function itself given to `ExpensiveComputationComponent` is the _same_ function every time. We can use `useCallback` to make sure that React is handing _the same fibonacci_ to `ExpensiveComputationComponent` every time so it passes its `React.memo` check every single time. Now it's only if `count` changes will it actually re-render (as evidenced by the time.) 11 | 12 | Try removing the useCallback call and see if you get the the count to 40+ that the page crawls as it updates every second. 13 | 14 | [callback]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FCallback.js 15 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/H-uselayouteffect.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useLayoutEffect" 3 | description: "" 4 | --- 5 | 6 | [Component][layout-effect] 7 | 8 | `useLayoutEffect` is almost the same as `useEffect` except that it's synchronous to render as opposed to scheduled like `useEffect` is. If you're migrating from a class component to a hooks-using function component, this can be helpful too because `useLayout` runs at the same time as `componentDidMount` and `componentDidUpdate` whereas `useEffect` is scheduled after. This should be a temporary fix. 9 | 10 | The only time you _should_ be using `useLayoutEffect` is to measure DOM nodes for things like animations. In the example, I measure the textarea after every time you click on it (the onClick is to force a re-render.) This means you're running render twice but it's also necessary to be able to capture the correct measurments. 11 | 12 | [layout-effect]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FLayoutEffect.js 13 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/I-useimperativehandle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useImperativeHandle" 3 | description: "" 4 | --- 5 | 6 | [Component][imperative-handle] 7 | 8 | Here's one you will likely never directly use but you may use libraries that use it for you. We're going to use it in conjunction with another feature called `forwardRef` that again, you probably won't use but libraries will use on your behalf. Let's explain first what it does using the example and then we'll explain the moving parts. 9 | 10 | In the example above, whenever you have an _invalid_ form, it will immediately focus the the first field that's invalid. If you look at the code, `ElaborateInput` is a child element so the parent component shouldn't have any access to the input contained inside the component. Those components are black boxes to their parents. All they can do is pass in props. So how do we accomplish it then? 11 | 12 | The first thing we use is `useImperativeHandle`. This allows us to customize methods on an object that is made available to the parents via the `useRef` API. Inside `ElaborateInput` we have two refs: one thate is the one that will be provided by the parent, forwarded through by wrapping the `ElaborateInput` component in a `forwardRef` call which will ten provide that second `ref` parameter in the function call, and then the `inputRef` which is being used to directly access the DOM so we can call `focus` on the DOM node directly. 13 | 14 | From the parent, we assign via `useRef` a ref to each of the `ElaborateInput`s which is then forwarded on each on via the `forwardRef`. Now, on these refs inside the parent component we have those methods that we made inside the child so we can call them when we need to. In this case, we'll calling the focus when the parent knows that the child has an error. 15 | 16 | Again, you probably use this directly but it's good to know it exists. Normally it's better to not use this hook and try to accomplish the same thing via props but sometimes it may be useful to break this one out. 17 | 18 | [imperative-handle]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FImperativeHandle.js 19 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/J-usedebugvalue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "useDebugValue" 3 | description: "" 4 | --- 5 | 6 | [Component][debug-value] 7 | 8 | Here's another hook that's baked into React that I don't foresee many of you using but I still want you to know it's there. It, like useImperativeHandle, is more built for library authors. 9 | 10 | useDebugValue allows you to surface information from your custom hook into the dev tools. This allows the developer who is consuming your hook (possibly you, possibly your coworker) to have whatever debugging information you choose to surfaced to them. If you're doing a little custom hook for your app (like the breed one we did in the Intro course) this probably isn't necessary. However if you're consuming a library that has hooks (like how react-router-dom has hooks) these can be useful hints to developers. 11 | 12 | Normally you'd just use the developer tools built into the browser but CodeSandbox has the dev tools built directly into it. Just know that normally you'd use the browser extension. 13 | 14 | [debug-value]: https://codesandbox.io/s/github/btholt/react-hooks-examples-v3/tree/master/?module=%2Fsrc%2FDebugValue.js 15 | -------------------------------------------------------------------------------- /lessons/09-hooks-in-depth/meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "icon": "search" 3 | } -------------------------------------------------------------------------------- /lessons/10-tailwindcss/A-css-and-react.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "" 3 | --- 4 | 5 | There are many ways to do CSS with React. Even just in this course I've done several ways with [style-components][sc] and [emotion][emotion]. Both of those are fantastic pieces of software and could definitely still be used today. As is the case when I teach this course I try to cover the latest material and what I think is the current state-of-the-art of the industry and today I think that is [TailwindCSS][tailwind]. 6 | 7 | Both style-components and emotion are libraries that execute in the JavaScript layer. They bring your CSS into your JavaScript. This allows you all the power of JavaScript to manipulate styles using JavaScript. 8 | 9 | Tailwind however is a different approach to this. And it bears mentioning that Tailwind isn't tied to React at all (whereas styled-components is and emotion mostly is.) Everything I'm showing you here is just incidentally using React (though Tailwind is particularly popular amongst React devs.) 10 | 11 | Let's get it set up. Run this: 12 | 13 | ```bash 14 | npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat@2.0.3 postcss@7.0.35 autoprefixer@9.8.6 @tailwindcss/postcss7-compat@2.0.3 15 | ``` 16 | 17 | - Under the hood, Parcel processes all your CSS with PostCSS with the autoprefixer plugin. This works like Babel: it means you can write modern code and it'll make it backwards compatible with older browsers. Since we're modifying the PostCSS config (like we did with Babel earlier in this project in the Intro part) we have to give it the whole config now. 18 | - We're using Parcel 1 in this project. They're heads-down on making Parcel 2 a reality which supports PostCSS 8. Parcel 1 is stuck on PostCSS 7. Tailwind 2 requires PostCSS 8 but luckily they provide a compatibility library with PostCSS 7. That's what `npm:@tailwindcss/postcss7-compat@2.0.3` is doing: it's called an alias. We're installing `@tailwindcss/postcss7-compat` and then aliasing it to `tailwindcss`. If you're brave you could try upgrading to Parcel 2 and then this wouldn't be necessary. 19 | 20 | Okay, now let's get our Tailwind project going. Run `npx tailwindcss init`. Like `tsc init` for TypeScript, this will spit out a basic starting config in tailwind.config.js. Should look like 21 | 22 | ```javascript 23 | module.exports = { 24 | purge: [], 25 | darkMode: false, // or 'media' or 'class' 26 | theme: { 27 | extend: {}, 28 | }, 29 | variants: {}, 30 | plugins: [], 31 | }; 32 | ``` 33 | 34 | Now, let's go modify our `style.css` file. 35 | 36 | ```css 37 | @tailwind base; 38 | @tailwind components; 39 | @tailwind utilities; 40 | ``` 41 | 42 | > If you're seeing Visual Studio Code give you a warning about unknown at rules in your style.css and it bothers you, open settings, search for `css.lint.unknownAtRules` and set that to ignore. 43 | 44 | This is how we include all the things we need from Tailwind. This is what allows Tailwind to bootstrap and only include the CSS you need to make your app run. 45 | 46 | > There's a great Visual Studio Code extension you should install here: [Tailwind CSS IntelliSense][tw]. 47 | 48 | Lastly, we have to create `.postcssrc` in root directory. 49 | 50 | ```json 51 | { 52 | "plugins": { 53 | "autoprefixer": {}, 54 | "tailwindcss": {} 55 | } 56 | } 57 | 58 | Now if you run your app you should the React app (and all the functionality should work) but it won't have any style. We're going to quickly restyle this whole app to show you how great Tailwind is and how quickly it lets you go. 59 | 60 | [tw]: https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss 61 | [sc]: https://btholt.github.io/complete-intro-to-react/ 62 | [emotion]: https://btholt.github.io/complete-intro-to-react-v5/emotion 63 | [tailwind]: https://tailwindcss.com/docs 64 | ``` 65 | -------------------------------------------------------------------------------- /lessons/10-tailwindcss/B-tailwind-basics.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "" 3 | --- 4 | 5 | I'm going to need you to suspend everything you know about CSS best practices for this section. At the start this going to feel gross and weird. But stick with me. I initially had similar feelings towards React too. 6 | 7 | We are not going to be writing _any_ CSS (well, one little bit but THAT'S IT.) Tailwind is all about just using tiny utility classes and then having that be all the CSS you ever need. Let's see something _super basic_. 8 | 9 | > There are old class names from the previous CSS styling we had. Feel free to delete them or leave them. It doesn't matter. I haphazardly deleted them as I overwrote them with new class names. 10 | 11 | In App.js, put this: 12 | 13 | ```javascript 14 | // the div right inside 15 |
21 | […] 22 |
23 | ``` 24 | 25 | - The `p-0` and `m-0` is what Tailwind is a lot of: putting a lot of tiny utility classes on HTML elements. In this case: we're making it so the encapsulating div has zero padding and zero margin. If we wanted it to have a little of either, it'd `m-1` or `p-1`. There's \*-1 through 12 and then there it's more a random increase with 12, 14, 16, 20, 24, 28, 32, 36, 40, etc. all the way up to 96. There's also `-m-1` for _negative_ margins. There's also mt, ml, mr, mb for top, left, right, bottom and mx for left and right and my for top and bottom (these all apply to p as well.) 26 | - We do have to apply the background image via styles. You'll find you'll occasionally need to do it for things that Tailwind doesn't do (like URLs) but for the most part you shouldn't need to. 27 | 28 | Let's do the whole header now. 29 | 30 | ```javascript 31 |
32 | 33 | Adopt Me! 34 | 35 |
36 | ``` 37 | 38 | - That's more what you'll see! Long class strings. I imagine some of you are upset looking at this. To be honest it's still strange to me. But we're also skinning a whole app with _zero_ CSS so it's a pretty compelling experience. 39 | - Like p and m, we have w and h. `w-1` would have a tiny width. `w-full` is width: 100%. 40 | - `bg-gradient-to-b from-purple-400 via-pink-500 to-red-500` is a gradient just using classes. `bg-gradient-to-b` says it goes from the top to bottom (you can do -to-l, -to-r, or -to-t as well.) The from is the start. The via is a middle stop, and the to is the end. 41 | - The purple-400 is a purple color and the 400 is the _lightness_ of it. 50 is nearly white, 900 is as dark as the color gets. 42 | - You can set your own colors via the theme but the default ones are really good. 43 | - `text-6xl` is a really big text size. They use the sizes sm, md, lg, xl, 2xl, etc. up to 9xl. 44 | - `text-center` will do `text-align: center`. 45 | - `hover:` is how we do hover, focus, disabled, etc. It takes whatever is on the right and only applies it only when that state is true. (note: disabled doesn't work without some magic in our PostCSS 7 compat layer. We'll do that in a bit.) 46 | - Note: `` from react-router-dom will pass styles and classes down to the resulting `
` for you. 47 | 48 | Let's hop over to `SearchResults.js` (we're only doing SearchParams, I'll leave it to you to fix Details) 49 | 50 | ```javascript 51 |
52 |
{ 55 | e.preventDefault(); 56 | requestPets(); 57 | }} 58 | > 59 | […] 60 |
61 |
62 | ``` 63 | 64 | - `rounded-lg` is a "large" rounding of the corners i.e. border-radius. 65 | - `shadow-lg` is a "large" border shadow. 66 | - `flex` makes the display mode flex. `flex-col` makes it columns. `justify-center` makes it justify-content center. `items-center` makes it `align-items: center`. Net result is that you have centered horizontally and vertically items in a vertical direction. 67 | - `divide-y` is pretty cool. It puts nice dividing lines between all the children elements in the div. `divide-gray-900` means they're black lines. 68 | -------------------------------------------------------------------------------- /lessons/10-tailwindcss/C-tailwind-plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "" 3 | --- 4 | 5 | Our inputs look really gross. We could write our own components (basically reusable CSS classes, what a novel idea) but we're just going to use the good ones that Tailwind provides out of the box. 6 | 7 | Run `npm install -D @tailwindcss/forms@0.2.1`. 8 | 9 | Put this into your tailwind.config.js 10 | 11 | ```javascript 12 | // replace plugins 13 | plugins: [require("@tailwindcss/forms")], 14 | ``` 15 | 16 | This will apply a bunch of default styles for all of our basic form elements. Tailwind has a pretty great plugin ecosystem. One of my favorites is the aspect-ratio one. CSS doesn't currently have a backwards compatible way of doing aspect ratios (e.g. keep this image in a square ratio) and this plugin makes a primitive that you can use like that. Super cool. 17 | 18 | Notice our location input still looks bad. With this plugin they (probably wisely) require you to add `type="text"` to the the input so they can have a good selector for it. So please go add that now to your text input. 19 | 20 | Let's finish making SearchParams looks nice. 21 | 22 | To each of the selects and inputs, add `className="w-60"` so they have a nice uniform width. 23 | 24 | To the breed selector, we want it to be grayed out when it's not available to use. However the PostCSS 7 version of Tailwind doesn't work with the `disabled: