├── .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 | [][course]
2 |
3 | [][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
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 |
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 |
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