├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ └── gatsby.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── csv.js ├── gatsby-config.js ├── gatsby-node.js ├── lessons ├── action-creators.md ├── babel.md ├── basic-react-testing.md ├── browserslist.md ├── class-components.md ├── class-properties.md ├── code-splitting.md ├── component-composition.md ├── components.md ├── conclusion.md ├── context.md ├── css-and-react.md ├── custom-hooks.md ├── dispatching-actions.md ├── end-of-intermediate.md ├── error-boundaries.md ├── eslint.md ├── git.md ├── grid-and-breakpoints.md ├── handling-user-input.md ├── hooks.md ├── images │ ├── BRAND-WHearts.png │ ├── CLASS-WALLPAPER-A.jpg │ ├── CLASS-WALLPAPER-B.jpg │ ├── HEADER-A.png │ ├── TOC-A.png │ ├── Wordmark-XL.png │ └── brian.JPG ├── intermediate-react-v3.md ├── intro.md ├── istanbul.md ├── jsx.md ├── managing-state-in-class-components.md ├── mocks.md ├── npm.md ├── parcel.md ├── portals-and-refs.md ├── positioning.md ├── prettier.md ├── providers.md ├── pure-react.md ├── react-dev-tools.md ├── react-router.md ├── reducers.md ├── redux-dev-tools.md ├── redux.md ├── server-side-rendering.md ├── snapshots.md ├── streaming-markup.md ├── tailwind-basics.md ├── tailwind-plugins.md ├── testing-custom-hooks.md ├── testing-react.md ├── testing-ui-interactions.md ├── ts-app.md ├── ts-carousel.md ├── ts-details.md ├── ts-error-boundary.md ├── ts-eslint.md ├── ts-modal.md ├── ts-pet.md ├── ts-results.md ├── ts-searchparams.md ├── ts-theme-context.md ├── ts-usebreedlist.md ├── usecallback.md ├── usecontext.md ├── usedebugvalue.md ├── useeffect-2.md ├── useeffect.md ├── useimperativehandle.md ├── uselayouteffect.md ├── usememo.md ├── usereducer.md ├── useref.md ├── usestate.md └── ways-to-expand-your-app.md ├── package-lock.json ├── package.json ├── src ├── components │ ├── TOCCard.css │ └── TOCCard.js ├── layouts │ ├── index.css │ └── index.js ├── pages │ ├── 404.js │ ├── index.css │ └── index.js ├── templates │ └── lessonTemplate.js └── util │ └── helpers.js └── static └── posterframe.jpg /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:import/errors", 5 | "plugin:react/recommended", 6 | "plugin:jsx-a11y/recommended", 7 | "prettier", 8 | "prettier/react" 9 | ], 10 | "rules": { 11 | "react/prop-types": 0, 12 | "jsx-a11y/label-has-for": 0, 13 | "no-console": 1 14 | }, 15 | "plugins": ["react", "import", "jsx-a11y"], 16 | "parser": "babel-eslint", 17 | "parserOptions": { 18 | "ecmaVersion": 2018, 19 | "sourceType": "module", 20 | "ecmaFeatures": { 21 | "jsx": true 22 | } 23 | }, 24 | "env": { 25 | "es6": true, 26 | "browser": true, 27 | "node": true 28 | }, 29 | "settings": { 30 | "react": { 31 | "version": "16.5.2" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/gatsby.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Gatsby Site to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: npm install, build, and csv 14 | run: | 15 | npm install 16 | npm run build 17 | npm run csv 18 | - name: Deploy site to gh-pages branch 19 | uses: alex-page/blazing-fast-gh-pages-deploy@v1.1.0 20 | with: 21 | repo-token: ${{ secrets.ACCESS_TOKEN }} 22 | site-directory: public 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .cache/ 5 | # Build directory 6 | public/ 7 | .DS_Store 8 | yarn-error.log 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Complete Intro to Linux and the CLI](/lessons/images/Wordmark-XL.png)][course] 2 | 3 | [![Frontend Masters](https://static.frontendmasters.com/assets/brand/logos/full.png)][fem] 4 | 5 | [Please click here][course] to head to the course website. 6 | 7 | # Issues and Pull Requests 8 | 9 | Please file issues and open pull requests here! Thank you! For issues with project files, either file issues on _this_ repo _or_ open a pull request on the projects repos. This repo itself is the course website. 10 | 11 | # Project Files 12 | 13 | [Please go here][project] for the project files. 14 | 15 | # License 16 | 17 | The content of this workshop is licensed under CC-BY-NC-4.0. Feel free to share freely but do not resell my content. 18 | 19 | The code, including the code of the site itself and the code in the exercises, are licensed under Apache 2.0. 20 | 21 | [fem]: https://frontendmasters.com/courses/complete-react-v6/ 22 | [course]: https://bit.ly/react-6 23 | [project]: https://github.com/btholt/citr-v6-project/ 24 | -------------------------------------------------------------------------------- /csv.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs").promises; 2 | const path = require("path"); 3 | const fm = require("front-matter"); 4 | const isUrl = require("is-url-superb"); 5 | const parseLinks = require("parse-markdown-links"); 6 | const { sorter } = require("./src/util/helpers"); 7 | const mdDir = process.env.MARKDOWN_DIR || path.join(__dirname, "lessons/"); 8 | const outputPath = 9 | process.env.OUTPUT_CSV_PATH || path.join(__dirname, "public/lessons.csv"); 10 | const linksOutputPath = 11 | process.env.LINKS_CSV_PATH || path.join(__dirname, "public/links.csv"); 12 | 13 | async function createCsv() { 14 | console.log(`making the markdown files into a CSV from ${mdDir}`); 15 | 16 | // get paths 17 | const allFiles = await fs.readdir(mdDir); 18 | const files = allFiles.filter(filePath => filePath.endsWith(".md")); 19 | 20 | // read paths, get buffers 21 | const buffers = await Promise.all( 22 | files.map(filePath => fs.readFile(path.join(mdDir, filePath))) 23 | ); 24 | 25 | // make buffers strings 26 | const contents = buffers.map(content => content.toString()); 27 | 28 | // make strings objects 29 | let frontmatters = contents.map(fm); 30 | 31 | // find all attribute keys 32 | const seenAttributes = new Set(); 33 | frontmatters.forEach(item => { 34 | Object.keys(item.attributes).forEach(attr => seenAttributes.add(attr)); 35 | }); 36 | const attributes = Array.from(seenAttributes.values()); 37 | 38 | if (attributes.includes("order")) { 39 | frontmatters = frontmatters.sort(sorter); 40 | } 41 | 42 | // get all data into an array 43 | let rows = frontmatters.map(item => { 44 | const row = attributes.map(attr => 45 | item.attributes[attr] ? JSON.stringify(item.attributes[attr]) : "" 46 | ); 47 | return row; 48 | }); 49 | 50 | // header row must be first row 51 | rows.unshift(attributes); 52 | 53 | // join into CSV string 54 | const csv = rows.map(row => row.join(",")).join("\n"); 55 | 56 | // write file out 57 | await fs.writeFile(outputPath, csv); 58 | 59 | console.log(`Wrote ${rows.length} rows to ${outputPath}`); 60 | 61 | // make links csv 62 | let longestLength = 0; 63 | let linksArray = frontmatters.map(row => { 64 | const links = parseLinks(row.body).filter(isUrl); 65 | longestLength = longestLength > links.length ? longestLength : links.length; 66 | const newRow = [row.attributes.order, row.attributes.title, ...links]; 67 | return newRow; 68 | }); 69 | 70 | if (longestLength) { 71 | // add title row 72 | linksArray = linksArray.map(array => { 73 | const lengthToFill = longestLength + 2 - array.length; 74 | return array.concat(Array.from({ length: lengthToFill }).fill("")); 75 | }); 76 | 77 | linksArray.unshift( 78 | ["order", "title"].concat( 79 | Array.from({ length: longestLength }).map((_, index) => `link${index}`) 80 | ) 81 | ); 82 | 83 | // join into CSV string 84 | const linksCsv = linksArray.map(row => row.join(",")).join("\n"); 85 | 86 | // write file out 87 | await fs.writeFile(linksOutputPath, linksCsv); 88 | 89 | console.log(`Wrote ${linksArray.length} rows to ${linksOutputPath}`); 90 | } 91 | } 92 | 93 | createCsv(); 94 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: "Complete Intro to React v6", 4 | subtitle: "& Intermediate React v3", 5 | description: 6 | "Brian Holt breaks down React into the most low level principles so that you can understand your whole stack, from front to back", 7 | keywords: [ 8 | "react", 9 | "react.js", 10 | "frontend masters", 11 | "parcel", 12 | "javascript", 13 | "brian holt", 14 | "js", 15 | "front end", 16 | "tailwind", 17 | ], 18 | }, 19 | pathPrefix: "/complete-intro-to-react-v6", 20 | plugins: [ 21 | `gatsby-plugin-layout`, 22 | { 23 | resolve: `gatsby-source-filesystem`, 24 | options: { 25 | path: `${__dirname}/lessons`, 26 | name: "markdown-pages", 27 | }, 28 | }, 29 | `gatsby-plugin-react-helmet`, 30 | `gatsby-plugin-sharp`, 31 | { 32 | resolve: `gatsby-transformer-remark`, 33 | options: { 34 | plugins: [ 35 | `gatsby-remark-autolink-headers`, 36 | `gatsby-remark-copy-linked-files`, 37 | `gatsby-remark-prismjs`, 38 | { 39 | resolve: `gatsby-remark-images`, 40 | options: { 41 | maxWidth: 800, 42 | linkImagesToOriginal: true, 43 | sizeByPixelDensity: false, 44 | }, 45 | }, 46 | ], 47 | }, 48 | }, 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | exports.createPages = ({ actions, graphql }) => { 4 | const { createPage } = actions; 5 | 6 | const lessonTemplate = path.resolve(`src/templates/lessonTemplate.js`); 7 | 8 | return graphql(` 9 | { 10 | allMarkdownRemark( 11 | sort: { order: DESC, fields: [frontmatter___order] } 12 | limit: 1000 13 | ) { 14 | edges { 15 | node { 16 | excerpt(pruneLength: 250) 17 | html 18 | id 19 | frontmatter { 20 | order 21 | path 22 | title 23 | } 24 | } 25 | } 26 | } 27 | } 28 | `).then(result => { 29 | if (result.errors) { 30 | return Promise.reject(result.errors); 31 | } 32 | 33 | result.data.allMarkdownRemark.edges.forEach(({ node }) => { 34 | createPage({ 35 | path: node.frontmatter.path, 36 | component: lessonTemplate 37 | }); 38 | }); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /lessons/action-creators.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Action Creators" 3 | path: "/action-creators" 4 | order: "14D" 5 | section: "Redux" 6 | description: "" 7 | --- 8 | 9 | Let's go make the action creators. These are the functions that the UI gives to the store to effect change: actions. These functions create actions. 10 | 11 | Create a new folder called actionCreators and put in changeTheme.js 12 | 13 | ```javascript 14 | export default function changeTheme(theme) { 15 | return { type: "CHANGE_THEME", payload: theme }; 16 | } 17 | ``` 18 | 19 | That's it! This one is the simplest form: create an object and return it. Some people will inline these action shapes in their React components. I prefer this because it makes refactors simple. Let's make the other two: 20 | 21 | changeLocation.js 22 | 23 | ```javascript 24 | export default function changeLocation(location) { 25 | return { type: "CHANGE_LOCATION", payload: location }; 26 | } 27 | ``` 28 | 29 | changeAnimal.js 30 | 31 | ```javascript 32 | export default function changeAnimal(location) { 33 | return { type: "CHANGE_ANIMAL", payload: location }; 34 | } 35 | ``` 36 | 37 | changeLocation.js 38 | 39 | ```javascript 40 | export default function changeBreed(location) { 41 | return { type: "CHANGE_BREED", payload: location }; 42 | } 43 | ``` 44 | 45 | That's it for action creators. In previous versions of this course, I taught how to do async actions so [check this out if you want to see that][v4-async]. there are a thousand flavors of how to do async with Redux. The most popular are [redux-observable][ro], [redux-saga][rs], [redux-promise][rp], and [redux-thunk][rt]. I showed how to use redux-thunk because it's simplest: the others are more powerful but more complex. 46 | 47 | [fsa]: https://github.com/redux-utilities/flux-standard-action 48 | [ro]: https://github.com/redux-observable/redux-observable 49 | [rs]: https://redux-saga.js.org/ 50 | [rp]: https://docs.psb.codes/redux-promise-middleware/ 51 | [rt]: https://github.com/reduxjs/redux-thunk 52 | -------------------------------------------------------------------------------- /lessons/babel.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/babel" 3 | title: "Babel" 4 | order: "3F" 5 | section: "JS Tools" 6 | 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." 7 | --- 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | 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: 14 | 15 | ```bash 16 | npm install -D @babel/core@7.12.16 @babel/preset-react@7.12.13 17 | ``` 18 | 19 | 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. 20 | 21 | Create a file called `.babelrc` in your home directory and put this in there: 22 | 23 | ```json 24 | { 25 | "presets": [ 26 | [ 27 | "@babel/preset-react", 28 | { 29 | "runtime": "automatic" 30 | } 31 | ] 32 | ] 33 | } 34 | ``` 35 | 36 | > 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. 37 | 38 | 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. 39 | 40 | If you needed to update a `preset-env` configuration (a semi-frequent occurrence) or add another transformation, you could do that here too. 41 | 42 | [releases]: https://github.com/parcel-bundler/parcel/releases 43 | -------------------------------------------------------------------------------- /lessons/basic-react-testing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Basic React Testing" 3 | path: "/basic-react-testing" 4 | order: "15B" 5 | section: "Testing" 6 | description: "" 7 | --- 8 | 9 | Let's write our first test for Pet.js. In general, here's my methodology for testing React: 10 | 11 | - Try to test functionality, not implementation. Make your tests interact with components as a user would, not as a developer would. This means you're trying to do more think of things like "what would a user see" or "if a user clicks a button a modal comes up" rather than "make sure this state is correct" or "ensure this library is called". This isn't a rule; sometimes you need to test those things too for assurance the app is working correctly. Use your best judgment. 12 | - Every UI I've ever worked on changes a lot. Try to not unnecessarily spin your wheels on things that aren't important and are likely to change. 13 | - In general when I encounter a bug that is important for me to go back and fix, I'll write a test that would have caught that bug. Actually what I'll do is _before_ I fix it, I'll write the test that fails. That way I fix it I'll know I won't regress back there. 14 | - Ask yourself what's important about your app and spend your time testing that. Ask yourself "if a user couldn't do X then the app is worthless" sort of questions and test those more thoroughly. If a user can't change themes then it's probably not the end of the world (a11y is important) so you can spend less time testing that but if a user can't log in then the app is worthless. Test that. 15 | 16 | Okay, create a new file called `Pet.test.js`. This naming convention is just habit. `Pet.spec.js` is common too. But as long as it's in the `__tests__` directory it doesn't much matter what you call it. 17 | 18 | ```javascript 19 | import { expect, test } from "@jest/globals"; 20 | import { render } from "@testing-library/react"; 21 | import Pet from "../Pet.js"; 22 | 23 | test("displays a default thumbnail", async () => { 24 | const pet = render(); 25 | 26 | const petThumbnail = await pet.findByTestId("thumbnail"); 27 | expect(petThumbnail.src).toContain("none.jpg"); 28 | }); 29 | ``` 30 | 31 | > 🚨 This doesn't work yet. That's intentional. 32 | 33 | This doesn't work!? Why? Well, turns out react-router-dom gets upset if you try to render its components without a Router above it. We could either go mock the APIs it's expecting (gross) or we could just give it a router. Let's do that. 34 | 35 | ```javascript 36 | // at top 37 | import { StaticRouter } from "react-router-dom"; 38 | 39 | // replace render 40 | const pet = render( 41 | 42 | 43 | 44 | ); 45 | ``` 46 | 47 | > 🚨 This doesn't work yet. That's intentional. 48 | 49 | The test doesn't pass? Oh, that's because it caught a bug! If you don't give it an images array, it just breaks. That defeats the purpose of having a default image! Let's go fix it in Pet.js. 50 | 51 | ```javascript 52 | if (images && images.length) { 53 | hero = images[0]; 54 | } 55 | ``` 56 | 57 | Now it should pass! 58 | 59 | Let's add one more test case for good measure to test the non-default use case. 60 | 61 | ```javascript 62 | test("displays a non-default thumbnail", async () => { 63 | const pet = render( 64 | 65 | 66 | 67 | ); 68 | 69 | const petThumbnail = await pet.findByTestId("thumbnail"); 70 | expect(petThumbnail.src).toContain("1.jpg"); 71 | }); 72 | ``` 73 | 74 | Bam! Some easy React testing there for you. 75 | -------------------------------------------------------------------------------- /lessons/browserslist.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/browserslist" 3 | title: "Browserslist" 4 | order: "3G" 5 | section: "JS Tools" 6 | 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." 7 | --- 8 | 9 | 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. 10 | 11 | Head to [browserslist.dev][dev]. 12 | 13 | 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. 14 | 15 | 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. 16 | 17 | In your package.json, add a new top level field called `browserslist` (notice the `s`, browser**s**list): 18 | 19 | ```json 20 | { 21 | … 22 | "browserslist": [ 23 | "last 2 Chrome versions" 24 | ] 25 | } 26 | ``` 27 | 28 | > 🏁 [Click here to see the state of the project up until now: 02-js-tools][step] 29 | 30 | [step]: https://github.com/btholt/citr-v6-project/tree/master/02-js-tools 31 | [dev]: https://browserslist.dev 32 | -------------------------------------------------------------------------------- /lessons/class-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Class Components" 3 | path: "/class-components" 4 | order: "5B" 5 | section: "React Capabilities" 6 | 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." 7 | --- 8 | 9 | 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. 10 | 11 | Let's go make Details.js as a class. 12 | 13 | ```javascript 14 | // replace Details.js 15 | import { Component } from "react"; 16 | import { withRouter } from "react-router-dom"; 17 | 18 | class Details extends Component { 19 | constructor() { 20 | super(); 21 | this.state = { loading: true }; 22 | } 23 | 24 | async componentDidMount() { 25 | const res = await fetch( 26 | `http://pets-v2.dev-apis.com/pets?id=${this.props.match.params.id}` 27 | ); 28 | const json = await res.json(); 29 | this.setState(Object.assign({ loading: false }, json.pets[0])); 30 | } 31 | 32 | render() { 33 | console.log(this.state); 34 | 35 | if (this.state.loading) { 36 | return

loading …

; 37 | } 38 | 39 | const { animal, breed, city, state, description, name } = this.state; 40 | 41 | return ( 42 |
43 |
44 |

{name}

45 |

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

46 | 47 |

{description}

48 |
49 |
50 | ); 51 | } 52 | } 53 | 54 | export default withRouter(Details); 55 | ``` 56 | 57 | - 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`. 58 | - 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. 59 | - `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. 60 | - 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. 61 | - `this.state` is the mutable state of the component (like useState). You'll use `this.setState` to mutate it (don't modify it directly.) 62 | - `this.props` comes from the parent component, similar to parameter given to the render functions that we pull props out of. 63 | - `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. 64 | 65 | ## Other lifecycle methods 66 | 67 | 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`. 68 | 69 | There are lots more you can check out in [the React docs here][docs]. 70 | 71 | [docs]: https://reactjs.org/docs/react-component.html 72 | -------------------------------------------------------------------------------- /lessons/class-properties.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Class Properties" 3 | path: "/class-properties" 4 | order: "5C" 5 | section: "React Capabilities" 6 | 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." 7 | --- 8 | 9 | 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.) 10 | 11 | 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: 12 | 13 | ```bash 14 | npm i -D @babel/plugin-proposal-class-properties@7.13.0 @babel/preset-env@7.13.5 @babel/eslint-parser@7.13.4 15 | ``` 16 | 17 | Now modify your `.babelrc` with the following: 18 | 19 | ```json 20 | { 21 | "presets": [ 22 | [ 23 | "@babel/preset-react", 24 | { 25 | "runtime": "automatic" 26 | } 27 | ], 28 | "@babel/preset-env" 29 | ], 30 | "plugins": ["@babel/plugin-proposal-class-properties"] 31 | } 32 | ``` 33 | 34 | 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. 35 | 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`: 36 | 37 | ```json 38 | { 39 | … 40 | "parser": "@babel/eslint-parser", 41 | … 42 | } 43 | ``` 44 | 45 | Now with this, we can modify Details to be as so: 46 | 47 | ```javascript 48 | // replace constructor 49 | state = { loading: true }; 50 | ``` 51 | 52 | Loads easier to read, right? 53 | -------------------------------------------------------------------------------- /lessons/code-splitting.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Code Splitting" 3 | path: "/code-splitting" 4 | order: "11A" 5 | description: "" 6 | section: "Code Splitting" 7 | --- 8 | 9 | > Please start with a fresh copy of this app: [Adopt Me!][app] 10 | 11 | Code splitting is _essential_ to having small application sizes, particularly with React. React is already forty-ish kilobytes just for the framework. This isn't huge but it's enough that it will slow down your initial page loads (by up to a second and a half on 2G speeds.) If you have a lot third party libraries on top of that, you've sunk yourself before they've even started loading your page. 12 | 13 | Enter code splitting. This allows us to identify spots where our code could be split and let Parcel do its magic in splitting things out to be loaded later. An easy place to do this would be at the route level. So let's try that first. 14 | 15 | Previous versions of this course use `react-loadable` to accomplish this. The latest version of React uses a combination of two things to accomplish this: `Suspense` and `React.lazy` 16 | 17 | Now add this to App.js 18 | 19 | ```javascript 20 | // import from React 21 | import { useState, StrictMode, lazy, Suspense } from "react"; 22 | // delete Details & Search params imports 23 | 24 | // above const App = 25 | const Details = lazy(() => import("./Details")); 26 | const SearchParams = lazy(() => import("./SearchParams")); 27 | 28 | // replace Router 29 | loading route …}> 30 | 31 | 32 |
33 | 34 | ; 35 | ``` 36 | 37 | That's it! Now Parcel will handle the rest of the glueing together for you!! Your initial bundle will load, then after that it will resolve that you want to load another piece, show the loading component (we show a lame amount of text but this could be fancy loading screen.) This Details page isn't too big but imagine if it had libraries like Moment or Lodash on it! It could be a big savings. 38 | 39 | Now our whole app loads async. What's great is that we can show the user _something_ (in this case just the header and the loading h1 but you should do better UX than that) and then load the rest of the content. You get to make your page fast. 40 | 41 | One more trick. Let's go make the Modal code load async! 42 | 43 | Refactor Details.js to be. 44 | 45 | ```javascript 46 | // import lazy 47 | import { Component, lazy } from "react"; 48 | 49 | // delete Modal import 50 | 51 | // below imports 52 | const Modal = lazy(() => import("./Modal")); 53 | ``` 54 | 55 | - That's it! Now we're not just splitting on route, we're splitting other places! You can split content _anywhere_! Load one component async while the other ones load sync. Use your imagination to achieve the best UX. 56 | - This cut out like 1KB, but the point to understand here is you can split places other than routes. Anywhere you're not using code upfront you can split and load later. 57 | - Notice we didn't have to use `` again. We already have a suspense component at the top of the app and so that still works! 58 | 59 | > 🏁 [Click here to see the state of the project up until now: code-splitting][step] 60 | 61 | [step]: https://github.com/btholt/citr-v6-project/tree/master/code-splitting 62 | [app]: https://github.com/btholt/citr-v6-project/tree/master/12-portals-and-refs 63 | -------------------------------------------------------------------------------- /lessons/component-composition.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: "4F" 3 | title: "Component Composition" 4 | path: "/component-composition" 5 | section: "Core React Concepts" 6 | description: "One component should do one thing. Brian shows you how to break down bigger components into smaller components." 7 | --- 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | Let's make a better display for our Pets components. Make a new file called Results.js. 16 | 17 | ```javascript 18 | import Pet from "./Pet"; 19 | 20 | const Results = ({ pets }) => { 21 | return ( 22 |
23 | {!pets.length ? ( 24 |

No Pets Found

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

{name}

77 |

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

78 |
79 |
80 | ); 81 | }; 82 | 83 | export default Pet; 84 | ``` 85 | 86 | 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. 87 | 88 | > 🏁 [Click here to see the state of the project up until now: 07-component-composition][step] 89 | 90 | [step]: https://github.com/btholt/citr-v6-project/tree/master/07-component-composition 91 | -------------------------------------------------------------------------------- /lessons/components.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/components" 3 | title: "Components" 4 | order: "2B" 5 | section: "No Frills React" 6 | description: "Brian teaches React without any frills: just you, some JavaScript, and the browser. No build step." 7 | --- 8 | 9 | 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. 10 | 11 | Modify your code so it looks like: 12 | 13 | ```javascript 14 | const Pet = () => { 15 | return React.createElement("div", {}, [ 16 | React.createElement("h1", {}, "Luna"), 17 | React.createElement("h2", {}, "Dog"), 18 | React.createElement("h2", {}, "Havanese"), 19 | ]); 20 | }; 21 | 22 | const App = () => { 23 | return React.createElement("div", {}, [ 24 | React.createElement("h1", {}, "Adopt Me!"), 25 | React.createElement(Pet), 26 | React.createElement(Pet), 27 | React.createElement(Pet), 28 | ]); 29 | }; 30 | 31 | ReactDOM.render(React.createElement(App), document.getElementById("root")); 32 | ``` 33 | 34 | > 🚨 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. 35 | 36 | - To make an element have multiple children, just pass it an array of elements. 37 | - 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. 38 | - Since we have a new `Pet` component, we can use it multiple times! We just use multiple calls to `React.createElement`. 39 | - 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. 40 | 41 | 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. 42 | 43 | ```javascript 44 | const Pet = (props) => { 45 | return React.createElement("div", {}, [ 46 | React.createElement("h1", {}, props.name), 47 | React.createElement("h2", {}, props.animal), 48 | React.createElement("h2", {}, props.breed), 49 | ]); 50 | }; 51 | 52 | const App = () => { 53 | return React.createElement("div", {}, [ 54 | React.createElement("h1", {}, "Adopt Me!"), 55 | React.createElement(Pet, { 56 | name: "Luna", 57 | animal: "Dog", 58 | breed: "Havanese", 59 | }), 60 | React.createElement(Pet, { 61 | name: "Pepper", 62 | animal: "Bird", 63 | breed: "Cockatiel", 64 | }), 65 | React.createElement(Pet, { name: "Doink", animal: "Cat", breed: "Mix" }), 66 | ]); 67 | }; 68 | 69 | ReactDOM.render(React.createElement(App), document.getElementById("root")); 70 | ``` 71 | 72 | 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! 73 | 74 | > 🏁 [Click here to see the state of the project up until now: 01-no-frills-react][step] 75 | 76 | [step]: https://github.com/btholt/citr-v6-project/tree/master/01-no-frills-react 77 | -------------------------------------------------------------------------------- /lessons/conclusion.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Conclusion of Complete Intro to React" 3 | path: "/conclusion" 4 | order: "7A" 5 | section: "End of Intro" 6 | description: "A quick note and congratulations from Brian for completing the Complete Intro to React v6!" 7 | --- 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | Good job getting this far and good luck on the next modules! 14 | -------------------------------------------------------------------------------- /lessons/context.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Context" 3 | path: "/context" 4 | order: "6B" 5 | section: "Special Case React Tools" 6 | description: "" 7 | --- 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | 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. 16 | 17 | 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. 18 | 19 | Make a new file called ThemeContext.js: 20 | 21 | ```javascript 22 | import { createContext } from "react"; 23 | 24 | const ThemeContext = createContext(["green", () => {}]); 25 | 26 | export default ThemeContext; 27 | ``` 28 | 29 | `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. 30 | 31 | 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. 32 | 33 | 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. 34 | 35 | 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: 36 | 37 | ```javascript 38 | // import useState and ThemeContext 39 | import { StrictMode, useState } from "react"; 40 | import ThemeContext from "./ThemeContext"; 41 | 42 | // top of App function body 43 | const theme = useState("darkblue"); 44 | 45 | // wrap the rest of the app 46 | […]; 47 | ``` 48 | 49 | - 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. 50 | - 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. 51 | - 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 ``. 52 | - Side note: if your context is _read only_ (meaning it will _never change_) you actually can skip wrapping your app in a Provider. 53 | 54 | Next let's go to `SearchParams.js`: 55 | 56 | ```javascript 57 | // import at top 58 | import React, { useState, useEffect, useContext } from "react"; 59 | import ThemeContext from "./ThemeContext"; 60 | 61 | // top of SearchParams function body 62 | const [theme] = useContext(ThemeContext); 63 | 64 | // replace button 65 | ; 66 | ``` 67 | 68 | - Now your button should be a beautiful shade of `darkblue`. 69 | - `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.) 70 | - 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. 71 | 72 | Let's go do this in Details.js 73 | 74 | ```javascript 75 | // import 76 | import ThemeContext from "./ThemeContext"; 77 | 78 | // replace button 79 | 80 | {([theme]) => ( 81 | 82 | )} 83 | ; 84 | ``` 85 | 86 | - This is how you use context inside of a class component. 87 | - 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. 88 | 89 | Lastly let's go make the theme changeable. Head back to SearchParams.js. 90 | 91 | ```javascript 92 | // also grab setTheme 93 | const [theme, setTheme] = useContext(ThemeContext); 94 | 95 | // below BreedDropdown 96 | ; 109 | ``` 110 | 111 | - This looks relatively similar to hooks, right? It should because it works the same! 112 | - 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. 113 | - 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. 114 | 115 | 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. 116 | 117 | > 🏁 [Click here to see the state of the project up until now: 11-context][step] 118 | 119 | [step]: https://github.com/btholt/citr-v6-project/tree/master/11-context 120 | [v4]: https://btholt.github.io/complete-intro-to-react-v4/context 121 | -------------------------------------------------------------------------------- /lessons/css-and-react.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CSS and React" 3 | path: "/css-and-react" 4 | order: "10A" 5 | section: "TailwindCSS" 6 | description: "" 7 | --- 8 | 9 | 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]. 10 | 11 | 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. 12 | 13 | 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.) 14 | 15 | Let's get it set up. Run this: 16 | 17 | ```bash 18 | 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 19 | ``` 20 | 21 | - 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. 22 | - 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. 23 | 24 | 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 25 | 26 | ```javascript 27 | module.exports = { 28 | purge: [], 29 | darkMode: false, // or 'media' or 'class' 30 | theme: { 31 | extend: {}, 32 | }, 33 | variants: {}, 34 | plugins: [], 35 | }; 36 | ``` 37 | 38 | Now, let's go modify our `style.css` file. 39 | 40 | ```css 41 | @tailwind base; 42 | @tailwind components; 43 | @tailwind utilities; 44 | ``` 45 | 46 | > 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. 47 | 48 | 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. 49 | 50 | > There's a great Visual Studio Code extension you should install here: [Tailwind CSS IntelliSense][tw]. 51 | 52 | Lastly, we have to create `.postcssrc` in root directory. 53 | 54 | ```json 55 | { 56 | "plugins": { 57 | "autoprefixer": {}, 58 | "tailwindcss": {} 59 | } 60 | } 61 | 62 | 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. 63 | 64 | [tw]: https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss 65 | [sc]: https://btholt.github.io/complete-intro-to-react/ 66 | [emotion]: https://btholt.github.io/complete-intro-to-react-v5/emotion 67 | [tailwind]: https://tailwindcss.com/docs 68 | -------------------------------------------------------------------------------- /lessons/custom-hooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: "4D" 3 | title: "Custom Hooks" 4 | path: "/custom-hooks" 5 | section: "Core React Concepts" 6 | description: "You can even make your own hooks! Brian shows how to extract logic out of a component to share a hook across components!" 7 | --- 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | Make a new file called `useBreedList.js` in src and put this in it. 14 | 15 | ```javascript 16 | import { useState, useEffect } from "react"; 17 | 18 | const localCache = {}; 19 | 20 | export default function useBreedList(animal) { 21 | const [breedList, setBreedList] = useState([]); 22 | const [status, setStatus] = useState("unloaded"); 23 | 24 | useEffect(() => { 25 | if (!animal) { 26 | setBreedList([]); 27 | } else if (localCache[animal]) { 28 | setBreedList(localCache[animal]); 29 | } else { 30 | requestBreedList(); 31 | } 32 | 33 | async function requestBreedList() { 34 | setBreedList([]); 35 | setStatus("loading"); 36 | const res = await fetch( 37 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}` 38 | ); 39 | const json = await res.json(); 40 | localCache[animal] = json.breeds || []; 41 | setBreedList(localCache[animal]); 42 | setStatus("loaded"); 43 | } 44 | }, [animal]); 45 | 46 | return [breedList, status]; 47 | } 48 | ``` 49 | 50 | - 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. 51 | - 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. 52 | 53 | Head over to SearchParam.js and put this in there. 54 | 55 | ```javascript 56 | import useBreedList from "./useBreedList"; 57 | 58 | // replace `const breeds = [];` 59 | const [breeds] = useBreedList(animal); 60 | ``` 61 | 62 | 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.) 63 | 64 | > 🏁 [Click here to see the state of the project up until now: 06-custom-hooks][step] 65 | 66 | [step]: https://github.com/btholt/citr-v6-project/tree/master/06-custom-hooks 67 | -------------------------------------------------------------------------------- /lessons/dispatching-actions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Dispatching Actions" 3 | path: "/dispatching-actions" 4 | order: "14F" 5 | section: "Redux" 6 | description: "" 7 | --- 8 | 9 | We're not quite done here. We got the reading part of Redux done but not the writing. Let's go do that too in SearchParams.js 10 | 11 | ```javascript 12 | import { useSelector, useDispatch } from "react-redux"; 13 | import changeLocation from "./actionCreators/changeLocation"; 14 | import changeTheme from "./actionCreators/changeTheme"; 15 | import changeAnimal from "./actionCreators/changeAnimal"; 16 | import changeBreed from "./actionCreators/changeBreed"; 17 | 18 | // up with other hooks 19 | const dispatch = useDispatch(); 20 | 21 | // change inputs 22 | 23 | dispatch(changeLocation(e.target.value))} 28 | /> 29 | 30 | 36 | 37 | 44 | 45 | 50 | ``` 51 | 52 | - This dispatching is so much nicer than it is with other API 53 | - The `useDispatch` hook gives you back a dispatching function so you can dispatch actions 54 | - That's really it! 55 | 56 | Now we're also using mapDispatchToState which lets us write functions to dispatch actions to Redux. Let's quickly add it to Details.js 57 | 58 | ```javascript 59 | // replace ThemeContext import 60 | import { connect } from "react-redux"; 61 | 62 | // remove all the ThemeContext stuff and the interior function 63 | // replace `context.theme` with just `this.props.theme` for the backgroundColor 64 | 65 | // bottom 66 | const mapStateToProps = ({ theme }) => ({ theme }); 67 | 68 | const WrappedDetails = connect(mapStateToProps)(Details); 69 | 70 | // replace DetailsWithRouter 71 | const ReduxWrappedDetails = connect(mapStateToProps)(Details); 72 | 73 | const DetailsWithRouter = withRouter(ReduxWrappedDetails); 74 | ``` 75 | 76 | Now it should work! Redux is a great piece of technology that adds a lot of complexity to your app. Don't add it lightly. I'd say you'd rarely want to start a new project using Redux: hit the problems first and then refactor it in. You just saw how. 77 | -------------------------------------------------------------------------------- /lessons/end-of-intermediate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Conclusion of Intermediate React" 3 | path: "/end-of-intermiate" 4 | order: "16A" 5 | section: "End of Intermediate" 6 | description: "" 7 | --- 8 | 9 | Thank you for sticking through this and hopefully you feel like you really dug into a good portion of the React ecosystem. With these additional tools in your React toolbox I feel like you're ready to take on any project that you decide to tackle. 10 | 11 | Please let me know how you liked the course and definitely [tweet at me][holtbt]! If you haven't I'd love a [star on this repo too][star]. 12 | 13 | Thanks again and I'll see you [back on Frontend Masters!][fem]! 14 | 15 | [fem]: https://frontendmasters.com/teachers/brian-holt/ 16 | [holtbt]: https://twitter.com/holtbt 17 | [star]: https://github.com/btholt/complete-intro-to-react-v6 18 | -------------------------------------------------------------------------------- /lessons/error-boundaries.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Error Boundaries" 3 | path: "/error-boundaries" 4 | order: "6A" 5 | section: "Special Case React Tools" 6 | description: "" 7 | --- 8 | 9 | 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. 10 | 11 | This will also catch 404s on our API if someone give it an invalid ID! 12 | 13 | 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 14 | 15 | ```javascript 16 | // mostly code from reactjs.org/docs/error-boundaries.html 17 | import { Component } from "react"; 18 | import { Link } from "react-router-dom"; 19 | 20 | class ErrorBoundary extends Component { 21 | state = { hasError: false }; 22 | static getDerivedStateFromError() { 23 | return { hasError: true }; 24 | } 25 | componentDidCatch(error, info) { 26 | console.error("ErrorBoundary caught an error", error, info); 27 | } 28 | render() { 29 | if (this.state.hasError) { 30 | return ( 31 |

32 | There was an error with this listing. Click here{" "} 33 | to back to the home page or wait five seconds. 34 |

35 | ); 36 | } 37 | 38 | return this.props.children; 39 | } 40 | } 41 | 42 | export default ErrorBoundary; 43 | ``` 44 | 45 | - Now anything that is a child of this component will have errors caught here. Think of this like a catch block from try/catch. 46 | - 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. 47 | - 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]. 48 | 49 | Let's go make Details use it. Go to Details.js 50 | 51 | ```javascript 52 | // add import 53 | import ErrorBoundary from "./ErrorBoundary"; 54 | 55 | // replace export 56 | const DetailsWithRouter = withRouter(Details); 57 | 58 | export default function DetailsErrorBoundary(props) { 59 | return ( 60 | 61 | 62 | 63 | ); 64 | } 65 | ``` 66 | 67 | - 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. 68 | - 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. 69 | 70 | 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. 71 | 72 | ```javascript 73 | // top 74 | import { Link, Redirect } from "react-router-dom"; 75 | 76 | // add redirect 77 | state = { hasError: false, redirect: false }; 78 | 79 | // under componentDidCatch 80 | componentDidUpdate() { 81 | if (this.state.hasError) { 82 | setTimeout(() => this.setState({ redirect: true }), 5000); 83 | } 84 | } 85 | 86 | // first thing inside render 87 | if (this.state.redirect) { 88 | return ; 89 | } } else if (this.state.hasError) { 90 | … 91 | } 92 | ``` 93 | 94 | - `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. 95 | - Rendering Redirect components is how you do redirects with React Router. You can also do it progamatically but I find this approach elegant. 96 | 97 | > 🏁 [Click here to see the state of the project up until now: 10-error-boundaries][step] 98 | 99 | [step]: https://github.com/btholt/citr-v6-project/tree/master/10-error-boundaries 100 | [azure]: https://azure.microsoft.com/en-us/services/monitor/?WT.mc_id=reactintro-github-brholt 101 | [sentry]: https://sentry.io/ 102 | [trackjs]: https://trackjs.com/ 103 | [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself 104 | -------------------------------------------------------------------------------- /lessons/eslint.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/eslint" 3 | title: "ESLint" 4 | order: "3C" 5 | section: "JS Tools" 6 | 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." 7 | --- 8 | 9 | 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. 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | Create this file called `.eslintrc.json`. 16 | 17 | ```json 18 | { 19 | "extends": ["eslint:recommended", "prettier"], 20 | "plugins": [], 21 | "parserOptions": { 22 | "ecmaVersion": 2021, 23 | "sourceType": "module", 24 | "ecmaFeatures": { 25 | "jsx": true 26 | } 27 | }, 28 | "env": { 29 | "es6": true, 30 | "browser": true, 31 | "node": true 32 | } 33 | } 34 | ``` 35 | 36 | 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. 37 | 38 | ```json 39 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet", 40 | ``` 41 | 42 | **ESLint will have a bunch of errors right now. Ignore them; we'll fix them in a sec.** 43 | 44 | Worth adding three things here: 45 | 46 | - 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`. 47 | - We can use our fix trick this way: `npm run lint -- --fix`. 48 | - We're going to both JS and JSX. 49 | 50 | ESLint is a cinch to get working with [Visual Studio Code][vscode]. Just down [the extension][vscode-eslint]. 51 | 52 | ## Alternatives 53 | 54 | - [jshint][jshint] 55 | 56 | [eslint]: https://eslint.org 57 | [vscode-eslint]: https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint 58 | [airbnb]: https://github.com/airbnb/javascript 59 | [jshint]: http://jshint.com/ 60 | [vscode]: https://code.visualstudio.com/ 61 | -------------------------------------------------------------------------------- /lessons/git.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/git" 3 | title: "Git" 4 | order: "3D" 5 | section: "JS Tools" 6 | description: "Git is a critical part of any JS project and Brian makes sure you have it set up." 7 | --- 8 | 9 | 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.) 10 | 11 | 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: 12 | 13 | ``` 14 | node_modules 15 | .cache/ 16 | dist/ 17 | .env 18 | .DS_Store 19 | coverage/ 20 | .vscode/ 21 | ``` 22 | 23 | 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] 24 | 25 | [nina]: https://frontendmasters.com/courses/git-in-depth/ 26 | -------------------------------------------------------------------------------- /lessons/grid-and-breakpoints.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Grid and Breakpoints" 3 | path: "/grid-and-breakpoints" 4 | order: "10D" 5 | section: "TailwindCSS" 6 | description: "" 7 | --- 8 | 9 | This is probably what most sold me on Tailwind CSS. Let's say we want to make a nice sort of tiled layout for our search results for our pets. Tailwind makes this so easy. Head to Results.js and let's see this. 10 | 11 | ```javascript 12 | // replace outermost div 13 |
[…]
14 | ``` 15 | 16 | lol what 17 | 18 | That just feels like it's cheating, right? Wow. 19 | 20 | - `grid` puts you into `display: grid`. 21 | - `gap-4` gives you the gutters between with the number representing how big. 22 | - `grid-cols-2` says how many you want per row. 23 | 24 | But we're not done here. Let's make it _responsive_ too. 25 | 26 | ```javascript 27 |
[…]
28 | ``` 29 | 30 | 🤯 31 | 32 | - The `sm:` is the small breakpoint which is larger than 640px apply this style (these can be adjusted and renamed) 33 | - The `lg:` is the large breakpoint is larger than 1024px. There's also md, xl, and 2xl too. 34 | 35 | We just did a fully responsive grid layout with no work. My much younger self is very upset by how much work I had to put into doing this right. This is so much easier than it used to be. 36 | -------------------------------------------------------------------------------- /lessons/handling-user-input.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: "4E" 3 | title: "Handling User Input" 4 | path: "/handling-user-input" 5 | section: "Core React Concepts" 6 | description: "Brian shows how to wire up your app to work with users interacting with the site." 7 | --- 8 | 9 | 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: 10 | 11 | ```javascript 12 | // inside render 13 | const [pets, setPets] = useState([]); 14 | 15 | // replace
16 | { 18 | e.preventDefault(); 19 | requestPets(); 20 | }} 21 | > 22 | ``` 23 | 24 | Now you should be able to see the network request go out whenever you submit the form. 25 | 26 | 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]. 27 | 28 | [docs]: https://reactjs.org/docs/events.html#supported-events 29 | -------------------------------------------------------------------------------- /lessons/hooks.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: "4B" 3 | title: "Hooks" 4 | path: "/hooks" 5 | section: "Core React Concepts" 6 | description: "React manages view state through a mechanism called hooks. Brian teaches you how to use them as you build components." 7 | --- 8 | 9 | Now we want to make it so you can modify what your search parameters are. Let's make a new route called SearchParams.js and have it accept these search parameters. 10 | 11 | ```javascript 12 | const SearchParams = () => { 13 | const location = "Seattle, WA"; 14 | return ( 15 |
16 | 17 | 21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | export default SearchParams; 28 | ``` 29 | 30 | > 🚨 ESLint is currently failing. We'll fix it in a sec. 31 | 32 | Now add it to your routes: 33 | 34 | ```javascript 35 | // delete Pet import, and add SearchParams 36 | import SearchParams from "./SearchParams"; 37 | 38 | // in App.js, replace all the Pets 39 | ; 40 | ``` 41 | 42 | Now navigate to http://localhost:1234 and see that your have one input box that says "Seattle, WA". Try and type in it. You'll see that you can't modify it. Why? Let's think about how React works: when you type in the input, React detects that a DOM event happens. When that happens, React thinks _something_ may have changed so it runs a re-render. Providing your render functions are fast, this is a very quick operation. It then diffs what's currently there and what its render pass came up with. It then updates the minimum amount of DOM necessary. 43 | 44 | Notice we're using `className` instead of `class` on the HTML element for CSS classes. This is because `class` is a reserved word in JS and JSX is still just JS. So instead they opted to use `className` which is the [name of the JS API][js-api] for interacting with class names. 45 | 46 | Like `className`, `htmlFor` is used because `for` is a reserved word in JS. 47 | 48 | So if we type in our input and it re-renders, what gets out in the `input` tag? Well, its value is tied to `location` and nothing changed that, so it remains the same. In other words, two way data binding is _not_ free in React. I say this is a feature because it makes you explicit on how you handle your data. Let's go make it work. 49 | 50 | ```javascript 51 | // in SearchParams.js 52 | import { useState } from "react"; 53 | 54 | // replace location 55 | const [location, updateLocation] = useState("Seattle, WA"); 56 | 57 | // replace input 58 | updateLocation(e.target.value)} 63 | />; 64 | ``` 65 | 66 | - This is called a hook. Other frameworks like Vue have started adopting it as well. 67 | - A hook called such (in my head) because it's a hook that gets caught every time the render function gets called. Because the hooks get called in the same order every single time, they'll always point to the same piece of state. Because of that they can be stateful: you can keep pieces of mutable state using hooks and then modify them later using their provided updater functions. 68 | - An _absolutely key_ concept for you to grasp is hooks rely on this strict ordering. As such, **do not put hooks inside if statements or loops**. If you do, you'll have insane bugs that involve `useState` returning _the wrong state_. If you see `useState` returning the wrong piece of state, this is likely what you did. 69 | - Because the previous point is so absolutely critical, the React team has provided us with a lint rule that help us not fall into that trap. That lint rule relies on us, the developers, to follow the convention of calling our hooks `useXxxxxx`. If you're willing to do that, the lint rules will guard you from calling the hooks out of order. 70 | - The argument given to `useState` is the default value. In our case, we gave it `"Seattle, WA"` as our default value. 71 | - `useState` returns to us an array with two things in it: the current value of that state and a function to update that function. We're using a feature of JavaScript called destructuring to get both of those things out of the array. 72 | - We use the `updateLocation` function in the `onChange` attribute of the input. Every time the input is typed into, it's going to call that function which calls `updateLocation` with what has been typed into the input. When `updateLocation` is called, React knows that its state has been modified and kicks off a re-render. 73 | - You can make your own custom hooks; `useState` is just one of many. 74 | - Historically, React has been written using `class`es with state being on the instance of the component. This is still a supported pattern in React. We'll see how to do it later. 75 | 76 | Let's add the ESLint rule. Run `npm install -D eslint-plugin-react-hooks@4.2.0`. Add this to ESLint: 77 | 78 | ```json 79 | { 80 | "extends": [ 81 | … 82 | "plugin:react-hooks/recommended", 83 | … 84 | ] 85 | } 86 | ``` 87 | 88 | > The order of extends isn't particularly important to us _except_ the Prettier ones _must_ be last. Those serve to turn off rules the others ones enable. 89 | 90 | Let's next make the animal drop down. 91 | 92 | ```javascript 93 | // under the imports 94 | const ANIMALS = ["bird", "cat", "dog", "rabbit", "reptile"]; 95 | 96 | // under location 97 | const [animal, updateAnimal] = useState(""); 98 | 99 | // under the location label 100 | ; 116 | ``` 117 | 118 | - You can use `useState` as many times as you need for various pieces of state! Again, this is why ordering is important because React relies on `useState` to be called in strictly the same order every time so it can give you the same piece of state. 119 | - Similar to above. We're using `onChange` and `onBlur` because it makes it more accessible. 120 | 121 | Let's make a third dropdown so you can select a breed as well as an animal. 122 | 123 | ```javascript 124 | // under your other state inside the component 125 | const [breed, updateBreed] = useState(""); 126 | const breeds = []; 127 | 128 | // under the animal label 129 | ; 146 | ``` 147 | 148 | So now we have a breed dropdown. The only really new thing we did was use the `disabled` property to disable the dropdown when you don't have any breeds. We're going to use the Petfinder API to request breeds based on the animal selected. If you select `dog`, you want to see poodles, labradors, and chihuahuas and parrots, tabbies, and Maine coons. Petfinder has and endpoint that if you give it a valid animal. We'll show you how to do that in the next lesson with effects. 149 | 150 | > 🏁 [Click here to see the state of the project up until now: 04-hooks][step] 151 | 152 | [babel]: https://babeljs.io/ 153 | [step]: https://github.com/btholt/citr-v6-project/tree/master/04-hooks 154 | [js-api]: https://developer.mozilla.org/en-US/docs/Web/API/Element/className 155 | -------------------------------------------------------------------------------- /lessons/images/BRAND-WHearts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btholt/complete-intro-to-react-v6/84da105e540c0b8b99e4c5f592b17f8075db921d/lessons/images/BRAND-WHearts.png -------------------------------------------------------------------------------- /lessons/images/CLASS-WALLPAPER-A.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btholt/complete-intro-to-react-v6/84da105e540c0b8b99e4c5f592b17f8075db921d/lessons/images/CLASS-WALLPAPER-A.jpg -------------------------------------------------------------------------------- /lessons/images/CLASS-WALLPAPER-B.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btholt/complete-intro-to-react-v6/84da105e540c0b8b99e4c5f592b17f8075db921d/lessons/images/CLASS-WALLPAPER-B.jpg -------------------------------------------------------------------------------- /lessons/images/HEADER-A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btholt/complete-intro-to-react-v6/84da105e540c0b8b99e4c5f592b17f8075db921d/lessons/images/HEADER-A.png -------------------------------------------------------------------------------- /lessons/images/TOC-A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btholt/complete-intro-to-react-v6/84da105e540c0b8b99e4c5f592b17f8075db921d/lessons/images/TOC-A.png -------------------------------------------------------------------------------- /lessons/images/Wordmark-XL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btholt/complete-intro-to-react-v6/84da105e540c0b8b99e4c5f592b17f8075db921d/lessons/images/Wordmark-XL.png -------------------------------------------------------------------------------- /lessons/images/brian.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/btholt/complete-intro-to-react-v6/84da105e540c0b8b99e4c5f592b17f8075db921d/lessons/images/brian.JPG -------------------------------------------------------------------------------- /lessons/intermediate-react-v3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Welcome" 3 | path: "/intermediate-react-v3" 4 | order: "8A" 5 | section: "Intermediate React v3" 6 | description: "Brian introduces you to Intermediate React v3 and explains how the course is structured." 7 | --- 8 | 9 | Welcome to Intermediate React v3 as taught by Brian Holt 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | 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. 16 | 17 | 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.) 18 | 19 | Alright! Let's have some fun with Intermdiate React v3! 20 | 21 | [intro]: https://btholt.github.io/complete-intro-to-react-v6/intro 22 | [citr]: https://github.com/btholt/citr-v6-project/ 23 | [project]: https://github.com/btholt/citr-v6-project/tree/master/12-portals-and-refs 24 | -------------------------------------------------------------------------------- /lessons/istanbul.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Istanbul" 3 | path: "/istanbul" 4 | order: "15G" 5 | section: "Testing" 6 | description: "" 7 | --- 8 | 9 | One last very cool trick that Jest has built into it: [Istanbul][istanbul]. Istanbul (which is not [Constantinople][they-might-be-giants]) is a tool which tells you _how much_ of your code that you're covering with tests. Via an interactive viewer you can see what lines are and aren't covered. This used to be annoying to set up by Jest just does it for you. 10 | 11 | Add the following command to your npm scripts: `"test:coverage": "jest --coverage"` and go ahead run `npm run test:coverage` and open the following file in your browser: `open coverage/lcov-report/index.html`. 12 | 13 | Here you can see the four files we've written tests for. One file, `useBreedList` is missing a line of coverage (click on the file name to see that): it's the line of reading back from the cache. That actually is a pretty important thing to cover as it could be a source of bugs (cache might as well be French for software bug). This can help identify gaps in your testing coverage. 14 | 15 | Lastly, add `coverage/` to your `.gitignore` since this shouldn't be checked in. 16 | 17 | [istanbul]: https://istanbul.js.org/ 18 | [they-might-be-giants]: https://youtu.be/vsQrKZcYtqg 19 | -------------------------------------------------------------------------------- /lessons/jsx.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/jsx" 3 | order: "4A" 4 | title: "JSX" 5 | section: "Core React Concepts" 6 | 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" 7 | --- 8 | 9 | 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. 10 | 11 | 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: 12 | 13 | ```javascript 14 | // delete the import 15 | 16 | const Pet = (props) => { 17 | return ( 18 |
19 |

{props.name}

20 |

{props.animal}

21 |

{props.breed}

22 |
23 | ); 24 | }; 25 | 26 | export default Pet; 27 | ``` 28 | 29 | > 🚨 ESLint is currently failing. We'll fix it at the end. 30 | 31 | 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. 32 | 33 | 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. 34 | 35 | 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. 36 | 37 | > 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! 38 | 39 | 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. 40 | 41 | So now JSX is demystified a bit, let's go convert App.js. 42 | 43 | ```javascript 44 | import { render } from "react-dom"; 45 | import Pet from "./Pet"; 46 | 47 | const App = () => { 48 | return ( 49 |
50 |

Adopt Me!

51 | 52 | 53 | 54 |
55 | ); 56 | }; 57 | 58 | render(, document.getElementById("root")); 59 | ``` 60 | 61 | > 🚨 ESLint is currently failing. We'll fix it at the end. 62 | 63 | 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. 64 | 65 | We now pass props down as we add attributes to an HTML tag. Pretty cool. 66 | 67 | ## ESLint + React 68 | 69 | 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. 70 | 71 | Run this: `npm install -D eslint-plugin-import@2.22.1 eslint-plugin-jsx-a11y@6.4.1 eslint-plugin-react@7.22.0` 72 | 73 | Update your .eslintrc.json to: 74 | 75 | ```javascript 76 | { 77 | "extends": [ 78 | "eslint:recommended", 79 | "plugin:import/errors", 80 | "plugin:react/recommended", 81 | "plugin:jsx-a11y/recommended", 82 | "prettier", 83 | "prettier/react" 84 | ], 85 | "rules": { 86 | "react/prop-types": 0, 87 | "react/react-in-jsx-scope": 0 88 | }, 89 | "plugins": ["react", "import", "jsx-a11y"], 90 | "parserOptions": { 91 | "ecmaVersion": 2021, 92 | "sourceType": "module", 93 | "ecmaFeatures": { 94 | "jsx": true 95 | } 96 | }, 97 | "env": { 98 | "es6": true, 99 | "browser": true, 100 | "node": true 101 | }, 102 | "settings": { 103 | "react": { 104 | "version": "detect" 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | 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. 111 | 112 | 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. 113 | 114 | - The import plugin helps ESLint catch commons bugs around imports, exports, and modules in general 115 | - jsx-a11y catches many bugs around accessibility that can accidentally arise using React, like not having an `alt` attribute on an `img` tag. 116 | - react is mostly common React bugs like not calling one of your props children. 117 | - `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. 118 | - `"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. 119 | 120 | Now your project should pass lint. 121 | 122 | > 🏁 [Click here to see the state of the project up until now: 03-jsx][step] 123 | 124 | [airbnb]: https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb 125 | [standard]: https://standardjs.com/ 126 | [step]: https://github.com/btholt/citr-v6-project/tree/master/03-jsx 127 | -------------------------------------------------------------------------------- /lessons/managing-state-in-class-components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Managing State in Class Components" 3 | path: "/managing-state-in-class-components" 4 | order: "5D" 5 | section: "React Capabilities" 6 | 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." 7 | --- 8 | 9 | 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. 10 | 11 | Let's make a nice photo carousel of the pictures for the animal now. Make a new file called Carousel.js: 12 | 13 | ```javascript 14 | import { Component } from "react"; 15 | 16 | class Carousel extends Component { 17 | state = { 18 | active: 0, 19 | }; 20 | 21 | static defaultProps = { 22 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"], 23 | }; 24 | 25 | render() { 26 | const { active } = this.state; 27 | const { images } = this.props; 28 | return ( 29 |
30 | animal 31 |
32 | {images.map((photo, index) => ( 33 | // eslint-disable-next-line 34 | animal thumbnail 40 | ))} 41 |
42 |
43 | ); 44 | } 45 | } 46 | 47 | export default Carousel; 48 | ``` 49 | 50 | Add the Carousel component to the Detail page. 51 | 52 | ```javascript 53 | // import at top 54 | import Carousel from "./Carousel"; 55 | 56 | // at top of Details function 57 | const { animal, breed, city, state, description, name, images } = this.state; 58 | 59 | // first component inside div.details 60 | ; 61 | ``` 62 | 63 | - 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. 64 | 65 | Let's make it so we can react to someone changing the photo on the carousel. 66 | 67 | ```javascript 68 | // add event listener 69 | handleIndexClick = event => { 70 | this.setState({ 71 | active: +event.target.dataset.index 72 | }); 73 | }; 74 | 75 | // above img 76 | // eslint-disable-next-line 77 | 78 | // add to img 79 | onClick={this.handleIndexClick} 80 | data-index={index} 81 | ``` 82 | 83 | - This is how you handle events in React class components. If it was keyboard handler, you'd do an onChange or onKeyUp, etc. handler. 84 | - 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. 85 | - The data attribute comes back as a string. We want it to be a number, hence the `+`. 86 | - We're doing bad accessibility stuff. But this makes it a lot simpler for learning for now. But don't do this in production. 87 | 88 | > 🏁 [Click here to see the state of the project up until now: 09-managing-state-in-class-components][step] 89 | 90 | [step]: https://github.com/btholt/citr-v6-project/tree/master/09-managing-state-in-class-components 91 | [babel]: https://babeljs.io/ 92 | -------------------------------------------------------------------------------- /lessons/mocks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Mocks" 3 | path: "/mocks" 4 | order: "15E" 5 | section: "Testing" 6 | description: "" 7 | --- 8 | 9 | Let's write a second test for actually making a request with our custom hook, useBreedList. But we have a problem: we don't actually want to `fetch` from our API. This can be slow and cause unnecessary load on a server or unnecessary complexity of spinning up a testing API. We can instead mock the call. A mock is a fake implementation. We _could_ write our own fake fetch but a good one already exists for Jest called jest-fetch-mock so let's install that. Run `npm install -D jest-fetch-mock@3.0.3`. 10 | 11 | We now need to make it so Jest implements this mock before we run our tests. We can make it run a set up script by putting this in our package.json: 12 | 13 | ```json 14 | { 15 | "jest": { 16 | "automock": false, 17 | "setupFiles": ["./src/setupJest.js"] 18 | } 19 | } 20 | ``` 21 | 22 | Then let's make a file in src called setupJest.js. 23 | 24 | ```javascript 25 | import { enableFetchMocks } from "jest-fetch-mock"; 26 | 27 | enableFetchMocks(); 28 | ``` 29 | 30 | Easy, right? Now it will fake all calls to fetch and we can provide fake API responses. We could provide a whole fake implementation here but let's do it in the testing code itself. If I was doing a lot of fake API calls, I might generate an [OpenAPI][openapi] spec and use that to generate a fake API but that's pretty advance. Start small and grow when you hit barriers. 31 | 32 | Okay, now go back to our useBreedList.test.js and add: 33 | 34 | ```javascript 35 | test("gives back breeds with an animal", async () => { 36 | const breeds = [ 37 | "Havanese", 38 | "Bichon Frise", 39 | "Poodle", 40 | "Maltese", 41 | "Golden Retriever", 42 | "Labrador", 43 | "Husky", 44 | ]; 45 | fetch.mockResponseOnce( 46 | JSON.stringify({ 47 | animal: "dog", 48 | breeds, 49 | }) 50 | ); 51 | const { result, waitForNextUpdate } = renderHook(() => useBreedList("dog")); 52 | 53 | await waitForNextUpdate(); 54 | 55 | const [breedList, status] = result.current; 56 | expect(status).toBe("loaded"); 57 | expect(breedList).toEqual(breeds); 58 | }); 59 | ``` 60 | 61 | The `waitForNextUpdate` allows us to sit back and wait for all of React's machinery to churn through the updates, effects, etc. until our data is ready for us to check on. And that's it! In general you should mock API calls. It will make tests run much faster and save unnecessary load on an API. 62 | 63 | [openapi]: https://swagger.io/ 64 | -------------------------------------------------------------------------------- /lessons/npm.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/npm" 3 | title: "npm" 4 | order: "3A" 5 | section: "JS Tools" 6 | description: "When putting any sort of JavaScript project together, npm is an essential tool. Brian talks about how to get started with npm." 7 | --- 8 | 9 | ## npm 10 | 11 | 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. 12 | 13 | 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. 14 | -------------------------------------------------------------------------------- /lessons/parcel.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/parcel" 3 | title: "Parcel" 4 | order: "3E" 5 | section: "JS Tools" 6 | 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." 7 | --- 8 | 9 | [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. 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | Install Parcel by doing `npm install -D parcel@1.12.3` 16 | 17 | > Parcel has recently renamed the npm package from `parcel-bundler` to just `parcel`. 18 | 19 | Now inside of your `package.json` put: 20 | 21 | ```json 22 | "scripts" { 23 | "dev": "parcel src/index.html" 24 | } 25 | ``` 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | 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. 32 | 33 | 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. 34 | 35 | Delete the two unpkg script tags in index.html 36 | 37 | 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: 38 | 39 | ```javascript 40 | import React from "react"; 41 | 42 | export default function Pet({ name, animal, breed }) { 43 | return React.createElement("div", {}, [ 44 | React.createElement("h1", {}, name), 45 | React.createElement("h2", {}, animal), 46 | React.createElement("h2", {}, breed), 47 | ]); 48 | } 49 | ``` 50 | 51 | Go to App.js 52 | 53 | ```javascript 54 | // at the top, under React imports 55 | import React from "react"; 56 | import ReactDOM from "react-dom"; 57 | import Pet from "./Pet"; 58 | 59 | // remove Pet component 60 | ``` 61 | 62 | 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. 63 | 64 | ## Alternatives 65 | 66 | - [Webpack][webpack] 67 | - [Browserify][browserify] 68 | - [esbuild][esbuild] 69 | - [Rollup][rollup] 70 | 71 | [browserify]: http://browserify.org/ 72 | [webpack]: https://webpack.js.org/ 73 | [parcel]: https://parceljs.org/ 74 | [rollup]: https://www.rollupjs.org/ 75 | [esbuild]: https://esbuild.github.io/ 76 | -------------------------------------------------------------------------------- /lessons/portals-and-refs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Portals and Refs" 3 | path: "/portals-and-refs" 4 | order: "6C" 5 | section: "Special Case React Tools" 6 | description: "" 7 | --- 8 | 9 | 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! 10 | 11 | First thing, let's go into index.html and add a separate mount point: 12 | 13 | ```html 14 | 15 | 16 | ``` 17 | 18 | This where the modal will actually be mounted whenever we render to this portal. Totally separate from our app root. 19 | 20 | Next create a file called Modal.js: 21 | 22 | ```javascript 23 | import React, { useEffect, useRef } from "react"; 24 | import { createPortal } from "react-dom"; 25 | 26 | const modalRoot = document.getElementById("modal"); 27 | 28 | const Modal = ({ children }) => { 29 | const elRef = useRef(null); 30 | if (!elRef.current) { 31 | elRef.current = document.createElement("div"); 32 | } 33 | 34 | useEffect(() => { 35 | modalRoot.appendChild(elRef.current); 36 | return () => modalRoot.removeChild(elRef.current); 37 | }, []); 38 | 39 | return createPortal(
{children}
, elRef.current); 40 | }; 41 | 42 | export default Modal; 43 | ``` 44 | 45 | - This will mount a div and mount inside of the portal whenever the Modal is rendered and then _remove_ itself whenever it's unrendered. 46 | - 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. 47 | - 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. 48 | - Down at the bottom we use React's `createPortal` to pass the children (whatever you put inside ``) to the portal div. 49 | 50 | Now go to Details.js and add: 51 | 52 | ```javascript 53 | // at the top 54 | import Modal from "./Modal"; 55 | 56 | // add showModal 57 | state = { loading: true, showModal: false }; 58 | 59 | // above render 60 | toggleModal = () => this.setState({ showModal: !this.state.showModal }); 61 | adopt = () => (window.location = "http://bit.ly/pet-adopt"); 62 | 63 | // add showModal 64 | const { 65 | animal, 66 | breed, 67 | city, 68 | state, 69 | description, 70 | name, 71 | images, 72 | showModal, 73 | } = this.state; 74 | 75 | // add onClick to ; 79 | 80 | // below description 81 | { 82 | showModal ? ( 83 | 84 |
85 |

Would you like to adopt {name}?

86 |
87 | 88 | 89 |
90 |
91 |
92 | ) : null 93 | } 94 | ``` 95 | 96 | - 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! 97 | - 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. 98 | 99 | 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.) 100 | 101 | > 🏁 [Click here to see the state of the project up until now: 12-portals-and-refs][step] 102 | 103 | [portal]: https://reactjs.org/docs/portals.html 104 | [step]: https://github.com/btholt/citr-v6-project/tree/master/12-portals-and-refs 105 | -------------------------------------------------------------------------------- /lessons/positioning.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Positioning" 3 | path: "/positioning" 4 | order: "10E" 5 | section: "TailwindCSS" 6 | description: "" 7 | --- 8 | 9 | Let's make Pet.js look great with a nice little overlay of their name, animal, breed, and location. Replace the returned markup with: 10 | 11 | ```javascript 12 | 13 |
14 | {name} 15 |
16 |
17 |

{name}

18 |

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

19 |
20 | 21 | ``` 22 | 23 | - We need to set the containing anchor link as both display block and relative positioning so we can reposition inside of it. 24 | - The `absolute` will make the name tag be absolutey positioned within the relatively positioned parent anchor link. 25 | - `bottom-0` and `left-0` will put our little name tag in the bottom left of the div. 26 | - The `bg-gradient-to-tr from-white to-transparent` gives us a fun little white-to-transparent gradient so it makes it easier to read the name tag. 27 | - `pr-2 pt-2` is a little right and top padding to extend the gradient. 28 | 29 | > 🚨 We did not do Details.js. I leave this as an exercise to you! 30 | 31 | > 🏁 [Click here to see the state of the project up until now: tailwind][step] 32 | 33 | [step]: https://github.com/btholt/citr-v6-project/tree/master/tailwind 34 | -------------------------------------------------------------------------------- /lessons/prettier.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/prettier" 3 | title: "Prettier" 4 | order: "3B" 5 | section: "JS Tools" 6 | 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." 7 | --- 8 | 9 | ## Code Quality 10 | 11 | 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. 12 | 13 | [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. 14 | 15 | 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. 16 | 17 | Need to tool around a bit with it before you trust it? [Go here][prettier-playground]. You can see how it works. 18 | 19 | Let's go integrate this into our project. It's _pretty_ easy (lol.) 20 | 21 | 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. 22 | 23 | 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]. 24 | 25 | 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. 26 | 27 | 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. 28 | 29 | ## npm/Yarn scripts 30 | 31 | 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. 32 | 33 | First run `npm install -D prettier`. `-D` means it's for development only. 34 | 35 | ```json 36 | "scripts": { 37 | "format": "prettier --write \"src/**/*.{js,jsx}\"" 38 | }, 39 | ``` 40 | 41 | 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. 42 | 43 | ## Alternatives 44 | 45 | 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. 46 | 47 | [jlongster]: https://twitter.com/jlongster 48 | [prettier]: https://github.com/prettier/prettier 49 | [prettier-playground]: https://prettier.io/playground/ 50 | [prettier-options]: https://prettier.io/docs/en/options.html 51 | [flow]: https://flow.org/ 52 | [prettier-ide]: https://github.com/prettier/prettier#editor-integration 53 | [ts]: https://www.typescriptlang.org/ 54 | [vscode]: https://code.visualstudio.com/?WT.mc_id=reactintro-github-brholt 55 | [vscode-prettier]: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode&WT.mc_id=reactintro-github-brholt 56 | -------------------------------------------------------------------------------- /lessons/providers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Providers" 3 | path: "/providers" 4 | order: "14E" 5 | section: "Redux" 6 | description: "" 7 | --- 8 | 9 | Okay, let's go integrate this now where context was being used before. Go to App.js: 10 | 11 | ```javascript 12 | // delete ThemeContext, useState import 13 | 14 | // import 15 | import { Provider } from "react-redux"; 16 | import store from "./store"; 17 | 18 | // delete useState call 19 | // delete ThemeContext 20 | 21 | // wrap app with 22 | […]; 23 | ``` 24 | 25 | Feels nice deleting a lot of code, right? 26 | 27 | Just like context makes your store available anywhere in your app, so does Provider. 28 | 29 | Now that Redux is available everywhere, let's go add it to SearchParams.js 30 | 31 | ```javascript 32 | // replace ThemeContext import 33 | // delete useContext import 34 | import { useSelector } from "react-redux"; 35 | 36 | // replace context and some usestate references 37 | const animal = useSelector((state) => state.animal); 38 | const location = useSelector((state) => state.location); 39 | const breed = useSelector((state) => state.breed); 40 | const theme = useSelector((state) => state.theme); 41 | ``` 42 | 43 | This is the newer, hooks-based API for react-redux. This allows you to provide it a selector function that will pluck the bit of state you need from Redux. Very clean, I quite like it. We'll see the older, connect-based one here in a bit. 44 | -------------------------------------------------------------------------------- /lessons/pure-react.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/pure-react" 3 | title: "Pure React" 4 | order: "2A" 5 | section: "No Frills React" 6 | description: "Brian teaches React without any frills: just you, some JavaScript, and the browser. No build step." 7 | --- 8 | 9 | Let's start by writing pure React. No compile step. No JSX. No Babel. No Webpack or Parcel. Just some JavaScript on a page. 10 | 11 | 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: 12 | 13 | ```javascript 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Adopt Me 23 | 24 | 25 | 26 |
not rendered
27 | 28 | 29 | 32 | 33 | 34 | 35 | ``` 36 | 37 | > 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]. 38 | 39 | 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". 40 | 41 | - Pretty standard HTML5 document. If this is confusing, I teach another course called [Intro to Web Dev][webdev] that can help you out. 42 | - 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. 43 | - We have two script tags. 44 | - 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. 45 | - 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. 46 | - 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. 47 | 48 | > 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! 49 | 50 | In the last script tag, put the following. 51 | 52 | ```javascript 53 | const App = () => { 54 | return React.createElement( 55 | "div", 56 | {}, 57 | React.createElement("h1", {}, "Adopt Me!") 58 | ); 59 | }; 60 | 61 | ReactDOM.render(React.createElement(App), document.getElementById("root")); 62 | ``` 63 | 64 | This is about the simplest React app you can build. 65 | 66 | - 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. 67 | - There are two types of components, function components and class components. This is a function component. We'll see class components shortly. 68 | - A function component _must_ return markup (which is what `React.createElement` generates.) 69 | - These component render functions _have_ to be fast. This function is going to be called a lot. It's a hot code path. 70 | - 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. 71 | - `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.) 72 | - 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.) 73 | - `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.) 74 | - 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. 75 | 76 | [webdev]: https://frontendmasters.com/courses/web-development-v2/ 77 | [logo]: https://raw.githubusercontent.com/btholt/react-redux-workshop/master/src/adopt-me.png 78 | [react17]: https://reactjs.org/blog/2020/10/20/react-v17.html 79 | [style]: https://raw.githubusercontent.com/btholt/citr-v6-project/master/01-no-frills-react/src/style.css 80 | -------------------------------------------------------------------------------- /lessons/react-dev-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: "4G" 3 | title: "React Dev Tools" 4 | path: "/react-dev-tools" 5 | section: "Core React Concepts" 6 | 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." 7 | --- 8 | 9 | React has some really great tools to enhance your developer experience. We'll go over a few of them here. 10 | 11 | ## `NODE_ENV=development` 12 | 13 | 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. 14 | 15 | 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. 16 | 17 | 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. 18 | 19 | ## Strict Mode 20 | 21 | 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. 22 | 23 | Go to App.js and wrap `` in the render call in ``. 24 | 25 | ```javascript 26 | // import at top 27 | import { StrictMode } from "react"; 28 | 29 | // replace render 30 | render( 31 | 32 | 33 | , 34 | document.getElementById("root") 35 | ); 36 | ``` 37 | 38 | ## Dev Tools 39 | 40 | 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. 41 | -------------------------------------------------------------------------------- /lessons/react-router.md: -------------------------------------------------------------------------------- 1 | --- 2 | path: "/react-router" 3 | title: "React Router" 4 | order: "5A" 5 | section: "React Capabilities" 6 | description: "One component should do one thing. Brian shows you how to break down bigger components into smaller components." 7 | --- 8 | 9 | > 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. 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | Let's quickly make a second page so we can switch between the two. Make file called Details.js. 16 | 17 | ```javascript 18 | const Details = () => { 19 | return

hi!

; 20 | }; 21 | 22 | export default Details; 23 | ``` 24 | 25 | 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`. 26 | 27 | Now we have two pages and the router available. Let's go make it ready to switch between the two. In `App.js`: 28 | 29 | ```javascript 30 | // at top 31 | import { BrowserRouter as Router, Route } from "react-router-dom"; 32 | import Details from "./Details"; 33 | 34 | // replace 35 | 36 | 37 |
38 | 39 | 40 | 41 | 42 | ; 43 | ``` 44 | 45 | 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! 46 | 47 | - React Router has a ton of features that we're not going to explain here. The docs do a great job. 48 | - The `:id` part is a variable. In `http://localhost:1234/details/1`, `1` would be the variable. 49 | - 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. 50 | 51 | On the Details page, notice that both pages render. It has to do with how React Router does routes. 52 | 53 | - React Router will render all components that the path match. 54 | - 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`. 55 | 56 | So let's make it match only one path with a component called Switch. 57 | 58 | ```javascript 59 | // replace react-router-dom import 60 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 61 | 62 | // replace jsx 63 |
64 | 65 |

Adopt Me!

66 | 67 | 68 |
69 | 70 | 71 | 72 | 73 | 74 | 75 |
; 76 | ``` 77 | 78 | Now notice it only renders one page at a time. 79 | 80 | So now let's make the two pages link to each other. Go to Pet.js. 81 | 82 | ```javascript 83 | // at top 84 | import { Link } from "react-router-dom"; 85 | 86 | // change wrapping
87 | 88 | […] 89 | ; 90 | ``` 91 | 92 | 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. 93 | 94 | 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: 95 | 96 | ```javascript 97 | import { useParams } from "react-router-dom"; 98 | 99 | const Details = () => { 100 | const { id } = useParams(); 101 | return

{id}

; 102 | }; 103 | 104 | export default Details; 105 | ``` 106 | 107 | The `useParams` hook is how you get params from React Router. It used to be through the props but now they prefer this API. 108 | 109 | Let's make the Adopt Me! header clickable too. 110 | 111 | ```javascript 112 | // import Link too 113 | import { Router, Link } from "react-router-dom"; 114 | 115 | // replace h1 116 |
117 | Adopt Me! 118 |
; 119 | ``` 120 | 121 | Now if you click the header, it'll take you back to the Results page. Cool. Now let's round out the Details page. 122 | 123 | > 🏁 [Click here to see the state of the project up until now: 08-react-router][step] 124 | 125 | [rr]: https://reacttraining.com/react-router/ 126 | [reach]: https://reach.tech/router/ 127 | [rf]: https://twitter.com/ryanflorence 128 | [step]: https://github.com/btholt/citr-v6-project/tree/master/08-react-router 129 | [remix]: https://remix.run 130 | -------------------------------------------------------------------------------- /lessons/reducers.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Reducers" 3 | path: "/reducers" 4 | order: "14B" 5 | section: "Redux" 6 | description: "" 7 | --- 8 | 9 | Make a new folder in src called `reducers`. Create a file called `index.js` in reducers and put: 10 | 11 | ```javascript 12 | import { combineReducers } from "redux"; 13 | import location from "./location"; 14 | 15 | export default combineReducers({ 16 | location, 17 | }); 18 | ``` 19 | 20 | combineReducers is a convenience function from Redux so you don't have to write your own root reducer. You can if you want to; this is just a bit easier. So now we have a root reducer that will delegate all changed to the `location` key to this reducer. So let's go make it. Make a file called `location.js` and put in it: 21 | 22 | ```javascript 23 | export default function location(state = "Seattle, WA", action) { 24 | switch (action.type) { 25 | case "CHANGE_LOCATION": 26 | return action.payload; 27 | default: 28 | return state; 29 | } 30 | } 31 | ``` 32 | 33 | Not very difficult. A reducer takes an old state, an action, and combines those things to make a state. In this case, if the state is `San Francisco, CA` and some calls it with the action `{type: 'CHANGE_LOCATION': payload: 'Salt Lake City, UT' }` then the _new_ state location would be Salt Lake City, UT. 34 | 35 | A reducer must have a default state. In our case, using ES6 default params, we made Seattle, WA our default state. This is how Redux will initialize your store, by calling each of your reducers once to get a default state. 36 | 37 | The shape of the action object is up to you but there is a thing called [Flux Standard Action][fsa] that some people adhere to to make building tools on top of actions easier. I've not used any of those tools but I also don't have a good reason _not_ to use this shape so I do. In sum, make your action shapes be `{ type: <[String, Number], required>, payload: , error: , meta: }`. The type could in theory be a Symbol too but it messes up the dev tools. 38 | 39 | Reducers are synchronous: they cannot be async. They also must be pure with no side-effects. If you call a reducer 10,000,000 times with the same state and action, you should get the same answer on the 10,000,001st time. 40 | 41 | Okay, so now we understand how, once given a state and an action, we can make a reducer. We haven't made nor dispatched those actions yet but we're getting there. Let's make the other reducers. 42 | 43 | theme.js 44 | 45 | ```javascript 46 | export default function theme(state = "darkblue", action) { 47 | switch (action.type) { 48 | case "CHANGE_THEME": 49 | return action.payload; 50 | default: 51 | return state; 52 | } 53 | } 54 | ``` 55 | 56 | animal.js 57 | 58 | ```javascript 59 | export default function animal(state = "", action) { 60 | switch (action.type) { 61 | case "CHANGE_ANIMAL": 62 | return action.payload; 63 | default: 64 | return state; 65 | } 66 | } 67 | ``` 68 | 69 | breed.js 70 | 71 | ```javascript 72 | export default function breed(state = "", action) { 73 | switch (action.type) { 74 | case "CHANGE_BREED": 75 | return action.payload; 76 | default: 77 | return state; 78 | } 79 | } 80 | ``` 81 | 82 | index.js 83 | 84 | ```javascript 85 | import { combineReducers } from "redux"; 86 | import location from "./location"; 87 | import theme from "./theme"; 88 | 89 | export default combineReducers({ 90 | location, 91 | theme, 92 | animal, 93 | breed, 94 | }); 95 | ``` 96 | -------------------------------------------------------------------------------- /lessons/redux-dev-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Redux Dev Tools" 3 | path: "/redux-dev-tools" 4 | order: "14G" 5 | section: "Redux" 6 | description: "" 7 | --- 8 | 9 | Let's quickly try the dev tools: 10 | 11 | - [Firefox][fox] 12 | - [Chrome][chrome] 13 | 14 | Download the one you're using, open up your app, and mess around the Redux tab. You can time travel, auto-generate tests, modify state, see actions, all sorts of cool stuff. Another good reason to use Redux. 15 | 16 | Hopefully you're well informed on the boons and busts of introducing Redux. It's great, just be careful. 17 | 18 | [If you want a deeper dive, check out the Frontend Masters course on Redux!][fem] 19 | 20 | > 🏁 [Click here to see the state of the project up until now: redux][step] 21 | 22 | [step]: https://github.com/btholt/citr-v6-project/tree/master/redux 23 | [fox]: https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/ 24 | [chrome]: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en 25 | [fem]: https://frontendmasters.com/courses/redux-mobx/ 26 | -------------------------------------------------------------------------------- /lessons/redux.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Getting Started" 3 | path: "/redux-getting-started" 4 | order: "14A" 5 | section: "Redux" 6 | description: "" 7 | --- 8 | 9 | Redux is a well-known library that does state management for you, very similarly to how we used context. With context, you use the provider and consumer as a sort of portal to skip passing parameters through every component. With Redux, we're taking the state management _out_ of React entirely and moving it to a separate store. 10 | 11 | Why do we have Redux? 12 | 13 | 1. Context used to be a lot worse to use and less useful. This made Redux (or Redux-like) management tools the only option 14 | 1. Redux code is _extremely testable_. This is probably the most compelling reason to use it. Having your state mutation be broken up in such a way to make it easy to test is fantastic. This is also mitigated because we have `useReducer` now. 15 | 1. The debugging story is pretty good. 16 | 17 | So given that we do now have the next context API, how often will I use Redux? Never, I anticipate. I rarely had problems that Redux solved (they exist; I just didn't have them) and the few cases now where I would see myself using Redux I think React's context would cover it. But if Redux speaks to you, do it! Don't let me stop you. It's a great library. Just be cautious. And there are reasons to use it: if you have complex orchestrations of async data, Redux can be immensely useful and I _would_ use it for that. 18 | 19 | Okay, let's get started. React state management is pretty simple: call setState and let React re-render. That's it! Now there's a few steps involved. 20 | 21 | 1. User types in input box 22 | 1. Call action creator to get an action 23 | 1. Dispatch action to Redux 24 | 1. Redux inserts the action into the root reducer 25 | 1. The root reducer delegates that action to the correct reducer 26 | 1. The reducer returns a new state given the old state and the action object 27 | 1. That new state becomes the store's state 28 | 1. React is then called by Redux and told to update 29 | 30 | So what was one step became several. But each step of this is testable, and that's great. And it's explicit and verbose. It's long to follow, but it's an easy breadcrumb trailer to follow when things go awry. So let's start writing it: 31 | 32 | Run `npm install redux@4.0.5 react-redux@7.2.2`. Create store.js and put in it: 33 | 34 | ```javascript 35 | import { createStore } from "redux"; 36 | import reducer from "./reducers"; 37 | 38 | const store = createStore( 39 | reducer, 40 | typeof window === "object" && 41 | typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== "undefined" 42 | ? window.__REDUX_DEVTOOLS_EXTENSION__() 43 | : (f) => f 44 | ); 45 | 46 | export default store; 47 | ``` 48 | 49 | We're including the dev tools middleware (I'll show you at the end.) This is the base of a store: a reducer. A store is just basically a big object with prescribed ways of changing it. So let's go make our first reducer. 50 | -------------------------------------------------------------------------------- /lessons/server-side-rendering.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Server Side Rendering" 3 | path: "/server-side-rendering" 4 | order: "12A" 5 | description: "" 6 | section: "Server Side Rendering" 7 | --- 8 | 9 | > Please start with a fresh copy of this app: [Adopt Me!][app] 10 | 11 | Performance is a central concern for front end developers. We should always be striving to serve the leanest web apps that perform faster than humans can think. This is as much a game of psychology as it is a a technological challenge. It's a challenge of loading the correct content first so a user can see a site and begin to make a decision of what they want to do (scroll down, click a button, log in, etc.) and then be prepared for that action before they make that decision. 12 | 13 | Enter server-side rendering. This is a technique where you run React on your Node.js server _before_ you serve the request to the user and send down the first rendering of your website already done. This saves precious milliseconds+ on your site because otherwise the user has to download the HTML, then download the JavaScript, then execute the JS to get the app. In this case, they'll just download the HTML and see the first rendered page while React is loading in the background. 14 | 15 | While the total time to when the page is actually interactive is comparable, if a bit slower, the time to when the user _sees_ something for the first time should be much faster, hence why this is a popular technique. So let's give it a shot. 16 | 17 | First, we need to remove all references to `window` or anything browser related from a path that _could_ be called in Node. That means whenever we reference `window`, it'll have to be inside componentDidMount since componentDidMount doesn't get called in Node. 18 | 19 | We'll also have change where our app gets rendered. Make a new file called ClientApp.js. Put in there: 20 | 21 | ```javascript 22 | import { hydrate } from "react-dom"; 23 | import { BrowserRouter, BrowserRouter as Router } from "react-router-dom"; 24 | import App from "./App"; 25 | 26 | hydrate( 27 | 28 | 29 | , 30 | document.getElementById("root") 31 | ); 32 | ``` 33 | 34 | This code will only get run in the browser, so any sort of browser related stuff can safely be done here (like analytics.) We're also using `React.hydrate` instead of `React.render` because this will hydrate existing markup with React magic ✨ rather than render it from scratch. 35 | 36 | Because ClientApp.js will now be the entry point to the app, not App.js, we'll need to fix that in the script tag in index.html. Change it from App.js to ClientApp.js 37 | 38 | Let's go fix App.js now: 39 | 40 | ```javascript 41 | // remove react-dom import 42 | // remove BrowserRouter as Router from react-router-dom import 43 | 44 | // move to wrapping the render 45 | 46 | // remove Router from 47 | 48 | // replace render at bottom 49 | export default App; 50 | ``` 51 | 52 | The Modal makes reference to window in its modular scope, let's move that reference inside the render function: 53 | 54 | ```javascript 55 | // replace modalRoot assignment 56 | let modalRoot; 57 | 58 | // in function 59 | modalRoot = modalRoot ? modalRoot : document.getElementById("modal"); 60 | ``` 61 | 62 | Now Modal doesn't reference window in the modular scope but it _does_ in the render function. This means you can't render a modal on initial page load. Since it's using the DOM to attach the portal, that sort of makes sense. Be careful of that. We're using a ternary to only look it up on the first render. 63 | 64 | We need a few more modules. Run `npm install express@4.17.1` to get the framework we need for Node. 65 | 66 | Go change your index.html to use ClientApp.js instead of App.js 67 | 68 | ```html 69 | 70 | ``` 71 | 72 | Now in your package.json, add the following to your `"scripts"` 73 | 74 | ```json 75 | "build:client": "parcel build --public-url ./dist/ src/index.html", 76 | "build:server": "parcel build -d dist-server --target node server/index.js", 77 | "build": "npm run build:client && npm run build:server", 78 | "start": "npm -s run build && node dist-server/index.js" 79 | ``` 80 | 81 | This will allow us to build the app into static (pre-compiled, non-dev) assets and then start our server. This will then let us run Parcel on our Node.js code too so we can use our React code directly in our App as well. 82 | 83 | Let's finally go write our Node.js server: 84 | 85 | ```javascript 86 | import express from "express"; 87 | import { renderToString } from "react-dom/server"; 88 | import { StaticRouter } from "react-router-dom"; 89 | import fs from "fs"; 90 | import App from "../src/App"; 91 | 92 | const PORT = process.env.PORT || 3000; 93 | 94 | const html = fs.readFileSync("dist/index.html").toString(); 95 | 96 | const parts = html.split("not rendered"); 97 | 98 | const app = express(); 99 | 100 | app.use("/dist", express.static("dist")); 101 | app.use((req, res) => { 102 | const staticContext = {}; 103 | const reactMarkup = ( 104 | 105 | 106 | 107 | ); 108 | 109 | res.status(staticContext.statusCode || 200); 110 | res.send(`${parts[0]}${renderToString(reactMarkup)}${parts[1]}`); 111 | res.end(); 112 | }); 113 | 114 | console.log(`listening on http://localhost:${PORT}`); 115 | app.listen(PORT); 116 | ``` 117 | 118 | - [Express.js][ex] is a Node.js web server framework. It's the most common one and a simple one to learn. 119 | - We'll be listening on port 3000 (http://locahost:**3000**) unless a environment variable is passed in saying otherwise. We do this because if you try to deploy this, you'll need to watch for PORT. 120 | - We'll statically serve what Parcel built. 121 | - Anything that Parcel _doesn't_ serve, will be given our index.html. This lets the client-side app handle all the routing. 122 | - We read the compiled HTML doc and split it around our `not rendered` statement. Then we can slot in our markup in between the divs, right where it should be. 123 | - We use renderToString to take our app and render it to a string we can serve as HTML, sandwiched inside our outer HTML. 124 | - The `staticContext` object allows us to see what status code came back from React Router so we can appropriately 404 on pages that don't exist. 125 | 126 | Run `npm run start` and then open http://localhost:3000 to see your server side rendered app. Notice it displays markup almost instantly. 127 | 128 | ## .gitignore 129 | 130 | Make sure you add `dist-server/` to your .gitignore here. We don't want to commit built code. 131 | 132 | > 🏁 [Click here to see the state of the project up until now: server-side-rendering-1][step] 133 | 134 | [step]: https://github.com/btholt/citr-v6-project/tree/master/server-side-rendering-1 135 | [app]: https://github.com/btholt/citr-v6-project/tree/master/12-portals-and-refs 136 | -------------------------------------------------------------------------------- /lessons/streaming-markup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Streaming Markup" 3 | path: "/streaming-markup" 4 | order: "12B" 5 | description: "" 6 | section: "Server Side Rendering" 7 | --- 8 | 9 | This is all cool, but we can make it _better_. 10 | 11 | With HTTP requests, you can actually send responses in chunks. This is called _streaming_ your request. When you stream a request, you send partially rendered bits to your client so that the browser can immediately start processing the HTML rather than getting one big payload at the end. Really, the biggest win is that browser can immediately start downloading CSS while you're still rendering your app. 12 | 13 | Let's see how to do this: 14 | 15 | ```javascript 16 | // change react-dom import 17 | import { renderToNodeStream } from "react-dom/server"; 18 | 19 | // replace app.use call 20 | app.use((req, res) => { 21 | res.write(parts[0]); 22 | const staticContext = {}; 23 | const reactMarkup = ( 24 | 25 | 26 | 27 | ); 28 | 29 | const stream = renderToNodeStream(reactMarkup); 30 | stream.pipe(res, { end: false }); 31 | stream.on("end", () => { 32 | res.status(staticContext.statusCode || 200); 33 | res.write(parts[1]); 34 | res.end(); 35 | }); 36 | }); 37 | ``` 38 | 39 | - Node has a native type called a stream. A stream, similar to a bash stream, is a stream of data that can be piped into something else. In this case, we have a Node stream of React markup being rendered. As each thing is rendered, React fires off a chunk that then can be sent to the user more quickly. 40 | - First thing we do is _immediately_ write the head to the user. This way they can grab the `` which the CSS `` tag in it, meaning they can start the CSS download ASAP. 41 | - From there we start streaming the React markup to the user. 42 | - After we finish with that stream, we write the end of the index.html page and close the connection. 43 | 44 | > 🏁 [Click here to see the state of the project up until now: server-side-rendering-2][step] 45 | 46 | [step]: https://github.com/btholt/citr-v6-project/tree/master/server-side-rendering-2 47 | -------------------------------------------------------------------------------- /lessons/tailwind-basics.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tailwind Basics" 3 | path: "/tailwind-basics" 4 | order: "10B" 5 | section: "TailwindCSS" 6 | description: "" 7 | --- 8 | 9 | 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. 10 | 11 | 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_. 12 | 13 | > 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. 14 | 15 | In App.js, put this: 16 | 17 | ```javascript 18 | // the div right inside 19 |
25 | […] 26 |
27 | ``` 28 | 29 | - 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.) 30 | - 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. 31 | 32 | Let's do the whole header now. 33 | 34 | ```javascript 35 |
36 | 37 | Adopt Me! 38 | 39 |
40 | ``` 41 | 42 | - 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. 43 | - Like p and m, we have w and h. `w-1` would have a tiny width. `w-full` is width: 100%. 44 | - `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. 45 | - 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. 46 | - You can set your own colors via the theme but the default ones are really good. 47 | - `text-6xl` is a really big text size. They use the sizes sm, md, lg, xl, 2xl, etc. up to 9xl. 48 | - `text-center` will do `text-align: center`. 49 | - `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.) 50 | - Note: `` from react-router-dom will pass styles and classes down to the resulting `
` for you. 51 | 52 | Let's hop over to `SearchResults.js` (we're only doing SearchParams, I'll leave it to you to fix Details) 53 | 54 | ```javascript 55 |
56 |
{ 59 | e.preventDefault(); 60 | requestPets(); 61 | }} 62 | > 63 | […] 64 |
65 |
66 | ``` 67 | 68 | - `rounded-lg` is a "large" rounding of the corners i.e. border-radius. 69 | - `shadow-lg` is a "large" border shadow. 70 | - `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. 71 | - `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. 72 | -------------------------------------------------------------------------------- /lessons/tailwind-plugins.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tailwind Plugins" 3 | path: "/tailwind-plugins" 4 | order: "10C" 5 | section: "TailwindCSS" 6 | description: "" 7 | --- 8 | 9 | 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. 10 | 11 | Run `npm install -D @tailwindcss/forms@0.2.1`. 12 | 13 | Put this into your tailwind.config.js 14 | 15 | ```javascript 16 | // replace plugins 17 | plugins: [require("@tailwindcss/forms")], 18 | ``` 19 | 20 | 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. 21 | 22 | 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. 23 | 24 | Let's finish making SearchParams looks nice. 25 | 26 | To each of the selects and inputs, add `className="w-60"` so they have a nice uniform width. 27 | 28 | 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: