├── .editorconfig
├── .gitignore
├── .prettierrc
├── 01-no-frills-react
└── src
│ ├── App.js
│ ├── index.html
│ └── style.css
├── 02-js-tools
├── .babelrc
├── .eslintrc.json
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── index.html
│ └── style.css
├── 03-jsx
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── index.html
│ └── style.css
├── 04-hooks
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── SearchParams.js
│ ├── index.html
│ └── style.css
├── 05-useeffect
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── SearchParams.js
│ ├── index.html
│ └── style.css
├── 06-custom-hooks
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── SearchParams.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 07-component-composition
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 08-react-router
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Details.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 09-managing-state-in-class-components
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 10-error-boundaries
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 11-context
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 12-portals-and-refs
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── LICENSE.md
├── README.md
├── code-splitting
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── redux
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── actionCreators
│ ├── changeAnimal.js
│ ├── changeBreed.js
│ ├── changeLocation.js
│ └── changeTheme.js
│ ├── index.html
│ ├── reducers
│ ├── animal.js
│ ├── breed.js
│ ├── index.js
│ ├── location.js
│ └── theme.js
│ ├── store.js
│ ├── style.css
│ └── useBreedList.js
├── server-side-rendering-1
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
├── server
│ └── index.js
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── ClientApp.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── server-side-rendering-2
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
├── server
│ └── index.js
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── ClientApp.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── tailwind
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .postcssrc
├── .prettierrc
├── package.json
├── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
└── tailwind.config.js
├── testing
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── __tests__
│ ├── Carousel.test.js
│ ├── Pet.test.js
│ ├── Results.test.js
│ ├── __snapshots__
│ │ └── Results.test.js.snap
│ └── useBreedList.test.js
│ ├── index.html
│ ├── setupJest.js
│ ├── style.css
│ └── useBreedList.js
├── typescript-1
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
├── src
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.js
│ ├── ErrorBoundary.js
│ ├── Modal.tsx
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
└── tsconfig.json
├── typescript-2
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
├── src
│ ├── APIResponsesTypes.ts
│ ├── App.js
│ ├── Carousel.js
│ ├── Details.tsx
│ ├── ErrorBoundary.js
│ ├── Modal.tsx
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.tsx
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
└── tsconfig.json
├── typescript-3
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
├── src
│ ├── APIResponsesTypes.ts
│ ├── App.js
│ ├── Carousel.tsx
│ ├── Details.tsx
│ ├── ErrorBoundary.tsx
│ ├── Modal.tsx
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── ThemeContext.tsx
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
└── tsconfig.json
├── typescript-4
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
├── src
│ ├── APIResponsesTypes.ts
│ ├── App.js
│ ├── Carousel.tsx
│ ├── Details.tsx
│ ├── ErrorBoundary.tsx
│ ├── Modal.tsx
│ ├── Pet.tsx
│ ├── Results.js
│ ├── SearchParams.tsx
│ ├── ThemeContext.tsx
│ ├── index.html
│ ├── style.css
│ └── useBreedList.tsx
└── tsconfig.json
└── typescript-5
├── .babelrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
├── src
├── APIResponsesTypes.ts
├── App.tsx
├── Carousel.tsx
├── Details.tsx
├── ErrorBoundary.tsx
├── Modal.tsx
├── Pet.tsx
├── Results.tsx
├── SearchParams.tsx
├── ThemeContext.tsx
├── index.html
├── style.css
└── useBreedList.tsx
└── tsconfig.json
/.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
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | dist-server/
5 | .env
6 | .DS_Store
7 | coverage/
8 | .vscode/
9 | package-lock.json
10 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/01-no-frills-react/src/App.js:
--------------------------------------------------------------------------------
1 | const Pet = (props) => {
2 | return React.createElement("div", {}, [
3 | React.createElement("h1", {}, props.name),
4 | React.createElement("h2", {}, props.animal),
5 | React.createElement("h2", {}, props.breed),
6 | ]);
7 | };
8 |
9 | const App = () => {
10 | return React.createElement("div", {}, [
11 | React.createElement("h1", {}, "Adopt Me!"),
12 | React.createElement(Pet, {
13 | name: "Luna",
14 | animal: "Dog",
15 | breed: "Havanese",
16 | }),
17 | React.createElement(Pet, {
18 | name: "Pepper",
19 | animal: "Bird",
20 | breed: "Cockatiel",
21 | }),
22 | React.createElement(Pet, { name: "Doink", animal: "Cat", breed: "Mix" }),
23 | ]);
24 | };
25 |
26 | ReactDOM.render(React.createElement(App), document.getElementById("root"));
27 |
--------------------------------------------------------------------------------
/01-no-frills-react/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/02-js-tools/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/02-js-tools/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended", "prettier"],
3 | "plugins": [],
4 | "parserOptions": {
5 | "ecmaVersion": 2021,
6 | "sourceType": "module",
7 | "ecmaFeatures": {
8 | "jsx": true
9 | }
10 | },
11 | "env": {
12 | "es6": true,
13 | "browser": true,
14 | "node": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/02-js-tools/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/02-js-tools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "7.18.0",
15 | "eslint-config-prettier": "8.1.0",
16 | "parcel": "1.12.3",
17 | "prettier": "2.2.1"
18 | },
19 | "dependencies": {
20 | "react": "17.0.1",
21 | "react-dom": "17.0.1"
22 | },
23 | "browserslist": [
24 | "last 2 Chrome versions"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/02-js-tools/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import Pet from "./Pet";
4 |
5 | const App = () => {
6 | return React.createElement("div", {}, [
7 | React.createElement("h1", {}, "Adopt Me!"),
8 | React.createElement(Pet, {
9 | name: "Luna",
10 | animal: "Dog",
11 | breed: "Havanese",
12 | }),
13 | React.createElement(Pet, {
14 | name: "Pepper",
15 | animal: "Bird",
16 | breed: "Cockatiel",
17 | }),
18 | React.createElement(Pet, { name: "Doink", animal: "Cat", breed: "Mix" }),
19 | ]);
20 | };
21 |
22 | ReactDOM.render(React.createElement(App), document.getElementById("root"));
23 |
--------------------------------------------------------------------------------
/02-js-tools/src/Pet.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function Pet({ name, animal, breed }) {
4 | return React.createElement("div", {}, [
5 | React.createElement("h1", {}, name),
6 | React.createElement("h2", {}, animal),
7 | React.createElement("h2", {}, breed),
8 | ]);
9 | }
10 |
--------------------------------------------------------------------------------
/02-js-tools/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/03-jsx/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ]
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/03-jsx/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "prettier"
8 | ],
9 | "rules": {
10 | "react/prop-types": 0,
11 | "react/react-in-jsx-scope": 0
12 | },
13 | "plugins": ["react", "import", "jsx-a11y"],
14 | "parserOptions": {
15 | "ecmaVersion": 2021,
16 | "sourceType": "module",
17 | "ecmaFeatures": {
18 | "jsx": true
19 | }
20 | },
21 | "env": {
22 | "es6": true,
23 | "browser": true,
24 | "node": true
25 | },
26 | "settings": {
27 | "react": {
28 | "version": "detect"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/03-jsx/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/03-jsx/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/03-jsx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/preset-react": "7.12.13",
16 | "eslint": "7.18.0",
17 | "eslint-config-prettier": "8.1.0",
18 | "eslint-plugin-import": "2.22.1",
19 | "eslint-plugin-jsx-a11y": "6.4.1",
20 | "eslint-plugin-react": "7.22.0",
21 | "parcel": "1.12.3",
22 | "prettier": "2.2.1"
23 | },
24 | "dependencies": {
25 | "react": "17.0.1",
26 | "react-dom": "17.0.1"
27 | },
28 | "browserslist": [
29 | "last 2 Chrome versions"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/03-jsx/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import Pet from "./Pet";
3 |
4 | const App = () => {
5 | return (
6 |
7 |
Adopt Me!
8 |
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | render(, document.getElementById("root"));
16 |
--------------------------------------------------------------------------------
/03-jsx/src/Pet.js:
--------------------------------------------------------------------------------
1 | const Pet = (props) => {
2 | return (
3 |
4 |
{props.name}
5 | {props.animal}
6 | {props.breed}
7 |
8 | );
9 | };
10 |
11 | export default Pet;
12 |
--------------------------------------------------------------------------------
/03-jsx/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/04-hooks/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/04-hooks/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parserOptions": {
16 | "ecmaVersion": 2021,
17 | "sourceType": "module",
18 | "ecmaFeatures": {
19 | "jsx": true
20 | }
21 | },
22 | "env": {
23 | "es6": true,
24 | "browser": true,
25 | "node": true
26 | },
27 | "settings": {
28 | "react": {
29 | "version": "detect"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/04-hooks/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/04-hooks/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/04-hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/preset-react": "7.12.13",
16 | "eslint": "7.18.0",
17 | "eslint-config-prettier": "8.1.0",
18 | "eslint-plugin-import": "2.22.1",
19 | "eslint-plugin-jsx-a11y": "6.4.1",
20 | "eslint-plugin-react": "7.22.0",
21 | "eslint-plugin-react-hooks": "4.2.0",
22 | "parcel": "1.12.3",
23 | "prettier": "2.2.1"
24 | },
25 | "dependencies": {
26 | "react": "17.0.1",
27 | "react-dom": "17.0.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/04-hooks/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 |
4 | const App = () => {
5 | return (
6 |
7 |
Adopt Me!
8 |
9 |
10 | );
11 | };
12 |
13 | render(, document.getElementById("root"));
14 |
--------------------------------------------------------------------------------
/04-hooks/src/Pet.js:
--------------------------------------------------------------------------------
1 | const Pet = (props) => {
2 | return (
3 |
4 |
{props.name}
5 | {props.animal}
6 | {props.breed}
7 |
8 | );
9 | };
10 |
11 | export default Pet;
12 |
--------------------------------------------------------------------------------
/04-hooks/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/05-useeffect/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/05-useeffect/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parserOptions": {
16 | "ecmaVersion": 2021,
17 | "sourceType": "module",
18 | "ecmaFeatures": {
19 | "jsx": true
20 | }
21 | },
22 | "env": {
23 | "es6": true,
24 | "browser": true,
25 | "node": true
26 | },
27 | "settings": {
28 | "react": {
29 | "version": "detect"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/05-useeffect/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/05-useeffect/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/05-useeffect/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/preset-react": "7.12.13",
16 | "eslint": "7.18.0",
17 | "eslint-config-prettier": "8.1.0",
18 | "eslint-plugin-import": "2.22.1",
19 | "eslint-plugin-jsx-a11y": "6.4.1",
20 | "eslint-plugin-react": "7.22.0",
21 | "eslint-plugin-react-hooks": "4.2.0",
22 | "parcel": "1.12.3",
23 | "prettier": "2.2.1"
24 | },
25 | "dependencies": {
26 | "react": "17.0.1",
27 | "react-dom": "17.0.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/05-useeffect/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 |
4 | const App = () => {
5 | return (
6 |
7 |
Adopt Me!
8 |
9 |
10 | );
11 | };
12 |
13 | render(, document.getElementById("root"));
14 |
--------------------------------------------------------------------------------
/05-useeffect/src/Pet.js:
--------------------------------------------------------------------------------
1 | const Pet = (props) => {
2 | return (
3 |
4 |
{props.name}
5 | {props.animal}
6 | {props.breed}
7 |
8 | );
9 | };
10 |
11 | export default Pet;
12 |
--------------------------------------------------------------------------------
/05-useeffect/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/06-custom-hooks/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/06-custom-hooks/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parserOptions": {
16 | "ecmaVersion": 2021,
17 | "sourceType": "module",
18 | "ecmaFeatures": {
19 | "jsx": true
20 | }
21 | },
22 | "env": {
23 | "es6": true,
24 | "browser": true,
25 | "node": true
26 | },
27 | "settings": {
28 | "react": {
29 | "version": "detect"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/06-custom-hooks/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/06-custom-hooks/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/06-custom-hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/preset-react": "7.12.13",
16 | "eslint": "7.18.0",
17 | "eslint-config-prettier": "8.1.0",
18 | "eslint-plugin-import": "2.22.1",
19 | "eslint-plugin-jsx-a11y": "6.4.1",
20 | "eslint-plugin-react": "7.22.0",
21 | "eslint-plugin-react-hooks": "4.2.0",
22 | "parcel": "1.12.3",
23 | "prettier": "2.2.1"
24 | },
25 | "dependencies": {
26 | "react": "17.0.1",
27 | "react-dom": "17.0.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/06-custom-hooks/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 |
4 | const App = () => {
5 | return (
6 |
7 |
Adopt Me!
8 |
9 |
10 | );
11 | };
12 |
13 | render(, document.getElementById("root"));
14 |
--------------------------------------------------------------------------------
/06-custom-hooks/src/Pet.js:
--------------------------------------------------------------------------------
1 | const Pet = (props) => {
2 | return (
3 |
4 |
{props.name}
5 | {props.animal}
6 | {props.breed}
7 |
8 | );
9 | };
10 |
11 | export default Pet;
12 |
--------------------------------------------------------------------------------
/06-custom-hooks/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/06-custom-hooks/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/07-component-composition/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/07-component-composition/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parserOptions": {
16 | "ecmaVersion": 2021,
17 | "sourceType": "module",
18 | "ecmaFeatures": {
19 | "jsx": true
20 | }
21 | },
22 | "env": {
23 | "es6": true,
24 | "browser": true,
25 | "node": true
26 | },
27 | "settings": {
28 | "react": {
29 | "version": "detect"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/07-component-composition/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/07-component-composition/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/07-component-composition/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/preset-react": "7.12.13",
16 | "eslint": "7.18.0",
17 | "eslint-config-prettier": "8.1.0",
18 | "eslint-plugin-import": "2.22.1",
19 | "eslint-plugin-jsx-a11y": "6.4.1",
20 | "eslint-plugin-react": "7.22.0",
21 | "eslint-plugin-react-hooks": "4.2.0",
22 | "parcel": "1.12.3",
23 | "prettier": "2.2.1"
24 | },
25 | "dependencies": {
26 | "react": "17.0.1",
27 | "react-dom": "17.0.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/07-component-composition/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 |
4 | const App = () => {
5 | return (
6 |
7 |
Adopt Me!
8 |
9 |
10 | );
11 | };
12 |
13 | render(, document.getElementById("root"));
14 |
--------------------------------------------------------------------------------
/07-component-composition/src/Pet.js:
--------------------------------------------------------------------------------
1 | const Pet = (props) => {
2 | const { name, animal, breed, images, location, id } = props;
3 |
4 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
5 | if (images.length) {
6 | hero = images[0];
7 | }
8 |
9 | return (
10 |
11 |
12 |

13 |
14 |
15 |
{name}
16 | {`${animal} — ${breed} — ${location}`}
17 |
18 |
19 | );
20 | };
21 |
22 | export default Pet;
23 |
--------------------------------------------------------------------------------
/07-component-composition/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/07-component-composition/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/07-component-composition/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/08-react-router/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/08-react-router/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parserOptions": {
16 | "ecmaVersion": 2021,
17 | "sourceType": "module",
18 | "ecmaFeatures": {
19 | "jsx": true
20 | }
21 | },
22 | "env": {
23 | "es6": true,
24 | "browser": true,
25 | "node": true
26 | },
27 | "settings": {
28 | "react": {
29 | "version": "detect"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/08-react-router/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/08-react-router/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/08-react-router/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/preset-react": "7.12.13",
16 | "eslint": "7.18.0",
17 | "eslint-config-prettier": "8.1.0",
18 | "eslint-plugin-import": "2.22.1",
19 | "eslint-plugin-jsx-a11y": "6.4.1",
20 | "eslint-plugin-react": "7.22.0",
21 | "eslint-plugin-react-hooks": "4.2.0",
22 | "parcel": "1.12.3",
23 | "prettier": "2.2.1"
24 | },
25 | "dependencies": {
26 | "react": "17.0.1",
27 | "react-dom": "17.0.1",
28 | "react-router-dom": "5.2.0"
29 | },
30 | "browserslist": [
31 | "last 2 Chrome versions"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/08-react-router/src/App.js:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 |
7 | const App = () => {
8 | return (
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | render(
28 |
29 |
30 | ,
31 | document.getElementById("root")
32 | );
33 |
--------------------------------------------------------------------------------
/08-react-router/src/Details.js:
--------------------------------------------------------------------------------
1 | import { useParams } from "react-router-dom";
2 |
3 | const Details = () => {
4 | const { id } = useParams();
5 | return {id}
;
6 | };
7 |
8 | export default Details;
9 |
--------------------------------------------------------------------------------
/08-react-router/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/08-react-router/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/08-react-router/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/08-react-router/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/eslint-parser": "7.13.4",
16 | "@babel/plugin-proposal-class-properties": "7.13.0",
17 | "@babel/preset-env": "7.13.5",
18 | "@babel/preset-react": "7.12.13",
19 | "eslint": "7.18.0",
20 | "eslint-config-prettier": "8.1.0",
21 | "eslint-plugin-import": "2.22.1",
22 | "eslint-plugin-jsx-a11y": "6.4.1",
23 | "eslint-plugin-react": "7.22.0",
24 | "eslint-plugin-react-hooks": "4.2.0",
25 | "parcel": "1.12.3",
26 | "prettier": "2.2.1"
27 | },
28 | "dependencies": {
29 | "react": "17.0.1",
30 | "react-dom": "17.0.1",
31 | "react-router-dom": "5.2.0"
32 | },
33 | "browserslist": [
34 | "last 2 Chrome versions"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
3 | import Details from "./Details";
4 | import SearchParams from "./SearchParams";
5 |
6 | const App = () => {
7 | return (
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | render(
27 |
28 |
29 | ,
30 | document.getElementById("root")
31 | );
32 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/Details.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { withRouter } from "react-router-dom";
3 | import Carousel from "./Carousel";
4 |
5 | class Details extends Component {
6 | state = { loading: true };
7 |
8 | async componentDidMount() {
9 | const res = await fetch(
10 | `http://pets-v2.dev-apis.com/pets?id=${this.props.match.params.id}`
11 | );
12 | const json = await res.json();
13 | this.setState(Object.assign({ loading: false }, json.pets[0]));
14 | }
15 |
16 | render() {
17 | if (this.state.loading) {
18 | return loading …
;
19 | }
20 |
21 | const {
22 | animal,
23 | breed,
24 | city,
25 | state,
26 | description,
27 | name,
28 | images,
29 | } = this.state;
30 |
31 | return (
32 |
33 |
34 |
35 |
{name}
36 |
{`${animal} — ${breed} — ${city}, ${state}`}
37 |
38 |
{description}
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | export default withRouter(Details);
46 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/10-error-boundaries/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/10-error-boundaries/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/10-error-boundaries/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/10-error-boundaries/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/10-error-boundaries/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/eslint-parser": "7.13.4",
16 | "@babel/plugin-proposal-class-properties": "7.13.0",
17 | "@babel/preset-env": "7.13.5",
18 | "@babel/preset-react": "7.12.13",
19 | "eslint": "7.18.0",
20 | "eslint-config-prettier": "8.1.0",
21 | "eslint-plugin-import": "2.22.1",
22 | "eslint-plugin-jsx-a11y": "6.4.1",
23 | "eslint-plugin-react": "7.22.0",
24 | "eslint-plugin-react-hooks": "4.2.0",
25 | "parcel": "1.12.3",
26 | "prettier": "2.2.1"
27 | },
28 | "dependencies": {
29 | "react": "17.0.1",
30 | "react-dom": "17.0.1",
31 | "react-router-dom": "5.2.0"
32 | },
33 | "browserslist": [
34 | "last 2 Chrome versions"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/App.js:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 |
7 | const App = () => {
8 | return (
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | render(
28 |
29 |
30 | ,
31 | document.getElementById("root")
32 | );
33 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/Details.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { withRouter } from "react-router-dom";
3 | import Carousel from "./Carousel";
4 | import ErrorBoundary from "./ErrorBoundary";
5 |
6 | class Details extends Component {
7 | state = { loading: true };
8 |
9 | async componentDidMount() {
10 | const res = await fetch(
11 | `http://pets-v2.dev-apis.com/pets?id=${this.props.match.params.id}`
12 | );
13 | const json = await res.json();
14 | this.setState(Object.assign({ loading: false }, json.pets[0]));
15 | }
16 |
17 | render() {
18 | if (this.state.loading) {
19 | return loading …
;
20 | }
21 |
22 | const {
23 | animal,
24 | breed,
25 | city,
26 | state,
27 | description,
28 | name,
29 | images,
30 | } = this.state;
31 |
32 | return (
33 |
34 |
35 |
36 |
{name}
37 |
{`${animal} — ${breed} — ${city}, ${state}`}
38 |
39 |
{description}
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | const DetailsWithRouter = withRouter(Details);
47 |
48 | export default function DetailsErrorBoundary(props) {
49 | return (
50 |
51 |
52 |
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/11-context/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/11-context/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/11-context/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/11-context/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/11-context/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/eslint-parser": "7.13.4",
16 | "@babel/plugin-proposal-class-properties": "7.13.0",
17 | "@babel/preset-env": "7.13.5",
18 | "@babel/preset-react": "7.12.13",
19 | "eslint": "7.18.0",
20 | "eslint-config-prettier": "8.1.0",
21 | "eslint-plugin-import": "2.22.1",
22 | "eslint-plugin-jsx-a11y": "6.4.1",
23 | "eslint-plugin-react": "7.22.0",
24 | "eslint-plugin-react-hooks": "4.2.0",
25 | "parcel": "1.12.3",
26 | "prettier": "2.2.1"
27 | },
28 | "dependencies": {
29 | "react": "17.0.1",
30 | "react-dom": "17.0.1",
31 | "react-router-dom": "5.2.0"
32 | },
33 | "browserslist": [
34 | "last 2 Chrome versions"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/11-context/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/11-context/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/11-context/src/Details.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { withRouter } from "react-router-dom";
3 | import ThemeContext from "./ThemeContext";
4 | import Carousel from "./Carousel";
5 | import ErrorBoundary from "./ErrorBoundary";
6 |
7 | class Details extends Component {
8 | state = { loading: true };
9 |
10 | async componentDidMount() {
11 | const res = await fetch(
12 | `http://pets-v2.dev-apis.com/pets?id=${this.props.match.params.id}`
13 | );
14 | const json = await res.json();
15 | this.setState(Object.assign({ loading: false }, json.pets[0]));
16 | }
17 |
18 | render() {
19 | if (this.state.loading) {
20 | return loading …
;
21 | }
22 |
23 | const {
24 | animal,
25 | breed,
26 | city,
27 | state,
28 | description,
29 | name,
30 | images,
31 | } = this.state;
32 |
33 | return (
34 |
35 |
36 |
37 |
{name}
38 |
{`${animal} — ${breed} — ${city}, ${state}`}
39 |
40 | {([theme]) => (
41 |
42 | )}
43 |
44 |
{description}
45 |
46 |
47 | );
48 | }
49 | }
50 |
51 | const DetailsWithRouter = withRouter(Details);
52 |
53 | export default function DetailsErrorBoundary(props) {
54 | return (
55 |
56 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/11-context/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/11-context/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/11-context/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/11-context/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/11-context/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 | not rendered
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/11-context/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/12-portals-and-refs/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/12-portals-and-refs/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/12-portals-and-refs/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/12-portals-and-refs/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/12-portals-and-refs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/eslint-parser": "7.13.4",
16 | "@babel/plugin-proposal-class-properties": "7.13.0",
17 | "@babel/preset-env": "7.13.5",
18 | "@babel/preset-react": "7.12.13",
19 | "eslint": "7.18.0",
20 | "eslint-config-prettier": "8.1.0",
21 | "eslint-plugin-import": "2.22.1",
22 | "eslint-plugin-jsx-a11y": "6.4.1",
23 | "eslint-plugin-react": "7.22.0",
24 | "eslint-plugin-react-hooks": "4.2.0",
25 | "parcel": "1.12.3",
26 | "prettier": "2.2.1"
27 | },
28 | "dependencies": {
29 | "react": "17.0.1",
30 | "react-dom": "17.0.1",
31 | "react-router-dom": "5.2.0"
32 | },
33 | "browserslist": [
34 | "last 2 Chrome versions"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal = ({ children }) => {
7 | const elRef = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | modalRoot.appendChild(elRef.current);
14 | return () => modalRoot.removeChild(elRef.current);
15 | }, []);
16 |
17 | return createPortal({children}
, elRef.current);
18 | };
19 |
20 | export default Modal;
21 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | These are the step-by-step examples for [the Complete Intro to React v6][citr] as taught for [Frontend Masters][fem] by [Brian Holt][twitter].
2 |
3 | - Feel free to open pull requests for errors on this repo.
4 | - For issues with this repo, please file them on [the course website's repo][site] (easier for me to track one instead of two repos.)
5 | - For issues with the code website (and not the code samples), [find the code here][site].
6 |
7 | [twitter]: https://twitter.com/holtbt
8 | [fem]: https://www.frontendmasters.com
9 | [citr]: https://bit.ly/react-6
10 | [site]: https://github.com/btholt/complete-intro-to-react-v6
11 |
--------------------------------------------------------------------------------
/code-splitting/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/code-splitting/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/code-splitting/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/code-splitting/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/code-splitting/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/eslint-parser": "7.13.4",
16 | "@babel/plugin-proposal-class-properties": "7.13.0",
17 | "@babel/preset-env": "7.13.5",
18 | "@babel/preset-react": "7.12.13",
19 | "eslint": "7.18.0",
20 | "eslint-config-prettier": "8.1.0",
21 | "eslint-plugin-import": "2.22.1",
22 | "eslint-plugin-jsx-a11y": "6.4.1",
23 | "eslint-plugin-react": "7.22.0",
24 | "eslint-plugin-react-hooks": "4.2.0",
25 | "parcel": "1.12.3",
26 | "prettier": "2.2.1"
27 | },
28 | "dependencies": {
29 | "react": "17.0.1",
30 | "react-dom": "17.0.1",
31 | "react-router-dom": "5.2.0"
32 | },
33 | "browserslist": [
34 | "last 2 Chrome versions"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/code-splitting/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode, lazy, Suspense } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import ThemeContext from "./ThemeContext";
5 |
6 | const Details = lazy(() => import("./Details"));
7 | const SearchParams = lazy(() => import("./SearchParams"));
8 |
9 | const App = () => {
10 | const theme = useState("darkblue");
11 | return (
12 |
13 |
14 | loading route …}>
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | render(
35 |
36 |
37 | ,
38 | document.getElementById("root")
39 | );
40 |
--------------------------------------------------------------------------------
/code-splitting/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/code-splitting/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/code-splitting/src/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal = ({ children }) => {
7 | const elRef = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | modalRoot.appendChild(elRef.current);
14 | return () => modalRoot.removeChild(elRef.current);
15 | }, []);
16 |
17 | return createPortal({children}
, elRef.current);
18 | };
19 |
20 | export default Modal;
21 |
--------------------------------------------------------------------------------
/code-splitting/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/code-splitting/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/code-splitting/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/code-splitting/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/code-splitting/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/redux/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/redux/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/redux/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/redux/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/eslint-parser": "7.13.4",
16 | "@babel/plugin-proposal-class-properties": "7.13.0",
17 | "@babel/preset-env": "7.13.5",
18 | "@babel/preset-react": "7.12.13",
19 | "eslint": "7.18.0",
20 | "eslint-config-prettier": "8.1.0",
21 | "eslint-plugin-import": "2.22.1",
22 | "eslint-plugin-jsx-a11y": "6.4.1",
23 | "eslint-plugin-react": "7.22.0",
24 | "eslint-plugin-react-hooks": "4.2.0",
25 | "parcel": "1.12.3",
26 | "prettier": "2.2.1"
27 | },
28 | "dependencies": {
29 | "react": "17.0.1",
30 | "react-dom": "17.0.1",
31 | "react-redux": "7.2.2",
32 | "react-router-dom": "5.2.0",
33 | "redux": "4.0.5"
34 | },
35 | "browserslist": [
36 | "last 2 Chrome versions"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/redux/src/App.js:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import { Provider } from "react-redux";
5 | import store from "./store";
6 | import Details from "./Details";
7 | import SearchParams from "./SearchParams";
8 |
9 | const App = () => {
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/redux/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/redux/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/redux/src/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal = ({ children }) => {
7 | const elRef = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | modalRoot.appendChild(elRef.current);
14 | return () => modalRoot.removeChild(elRef.current);
15 | }, []);
16 |
17 | return createPortal({children}
, elRef.current);
18 | };
19 |
20 | export default Modal;
21 |
--------------------------------------------------------------------------------
/redux/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/redux/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/redux/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/redux/src/actionCreators/changeAnimal.js:
--------------------------------------------------------------------------------
1 | export default function changeAnimal(theme) {
2 | return { type: "CHANGE_ANIMAL", payload: theme };
3 | }
4 |
--------------------------------------------------------------------------------
/redux/src/actionCreators/changeBreed.js:
--------------------------------------------------------------------------------
1 | export default function changeBreed(theme) {
2 | return { type: "CHANGE_BREED", payload: theme };
3 | }
4 |
--------------------------------------------------------------------------------
/redux/src/actionCreators/changeLocation.js:
--------------------------------------------------------------------------------
1 | export default function changeLocation(location) {
2 | return { type: "CHANGE_LOCATION", payload: location };
3 | }
4 |
--------------------------------------------------------------------------------
/redux/src/actionCreators/changeTheme.js:
--------------------------------------------------------------------------------
1 | export default function changeTheme(theme) {
2 | return { type: "CHANGE_THEME", payload: theme };
3 | }
4 |
--------------------------------------------------------------------------------
/redux/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/redux/src/reducers/animal.js:
--------------------------------------------------------------------------------
1 | export default function animal(state = "", action) {
2 | switch (action.type) {
3 | case "CHANGE_ANIMAL":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/redux/src/reducers/breed.js:
--------------------------------------------------------------------------------
1 | export default function breed(state = "", action) {
2 | switch (action.type) {
3 | case "CHANGE_BREED":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/redux/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import location from "./location";
3 | import theme from "./theme";
4 | import animal from "./animal";
5 | import breed from "./breed";
6 |
7 | export default combineReducers({
8 | location,
9 | theme,
10 | animal,
11 | breed,
12 | });
13 |
--------------------------------------------------------------------------------
/redux/src/reducers/location.js:
--------------------------------------------------------------------------------
1 | export default function location(state = "Seattle, WA", action) {
2 | switch (action.type) {
3 | case "CHANGE_LOCATION":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/redux/src/reducers/theme.js:
--------------------------------------------------------------------------------
1 | export default function theme(state = "darkblue", action) {
2 | switch (action.type) {
3 | case "CHANGE_THEME":
4 | return action.payload;
5 | default:
6 | return state;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/redux/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "redux";
2 | import reducer from "./reducers";
3 |
4 | const store = createStore(
5 | reducer,
6 | typeof window === "object" &&
7 | typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== "undefined"
8 | ? window.__REDUX_DEVTOOLS_EXTENSION__()
9 | : (f) => f
10 | );
11 |
12 | export default store;
13 |
--------------------------------------------------------------------------------
/redux/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/server-side-rendering-1/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "env": {
12 | "development": {
13 | "plugins": ["@babel/plugin-proposal-class-properties"]
14 | },
15 | "production": {
16 | "plugins": ["@babel/plugin-proposal-class-properties"]
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/server-side-rendering-1/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server-side-rendering-1/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | dist-server/
5 | .env
6 | .DS_Store
7 | coverage/
8 | .vscode/
9 |
--------------------------------------------------------------------------------
/server-side-rendering-1/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/server-side-rendering-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html",
10 | "build:client": "parcel build --public-url ./dist/ src/index.html",
11 | "build:server": "parcel build -d dist-server --target node server/index.js",
12 | "build": "npm run build:client && npm run build:server",
13 | "start": "npm -s run build && node dist-server/index.js"
14 | },
15 | "author": "Brian Holt ",
16 | "license": "Apache-2.0",
17 | "devDependencies": {
18 | "@babel/cli": "7.13.0",
19 | "@babel/core": "7.12.16",
20 | "@babel/eslint-parser": "7.13.4",
21 | "@babel/plugin-proposal-class-properties": "7.13.0",
22 | "@babel/preset-env": "7.13.5",
23 | "@babel/preset-react": "7.12.13",
24 | "eslint": "7.18.0",
25 | "eslint-config-prettier": "8.1.0",
26 | "eslint-plugin-import": "2.22.1",
27 | "eslint-plugin-jsx-a11y": "6.4.1",
28 | "eslint-plugin-react": "7.22.0",
29 | "eslint-plugin-react-hooks": "4.2.0",
30 | "parcel": "1.12.3",
31 | "prettier": "2.2.1"
32 | },
33 | "dependencies": {
34 | "express": "4.17.1",
35 | "react": "17.0.1",
36 | "react-dom": "17.0.1",
37 | "react-router-dom": "5.2.0"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/server-side-rendering-1/server/index.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { renderToString } from "react-dom/server";
3 | import { StaticRouter } from "react-router-dom";
4 | import fs from "fs";
5 | import App from "../src/App";
6 |
7 | const PORT = process.env.PORT || 3000;
8 |
9 | const html = fs.readFileSync("dist/index.html").toString();
10 |
11 | const parts = html.split("not rendered");
12 |
13 | const app = express();
14 |
15 | app.use("/dist", express.static("dist"));
16 | app.use((req, res) => {
17 | const staticContext = {};
18 | const reactMarkup = (
19 |
20 |
21 |
22 | );
23 |
24 | res.status(staticContext.statusCode || 200);
25 | res.send(`${parts[0]}${renderToString(reactMarkup)}${parts[1]}`);
26 | res.end();
27 | });
28 |
29 | console.log(`listening on http://localhost:${PORT}`);
30 | app.listen(PORT);
31 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { Route, Switch, Link } from "react-router-dom";
3 | import Details from "./Details";
4 | import SearchParams from "./SearchParams";
5 | import ThemeContext from "./ThemeContext";
6 |
7 | const App = () => {
8 | const theme = useState("darkblue");
9 | return (
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default App;
31 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/ClientApp.js:
--------------------------------------------------------------------------------
1 | import { hydrate } from "react-dom";
2 | import { BrowserRouter, BrowserRouter as Router } from "react-router-dom";
3 | import App from "./App";
4 |
5 | hydrate(
6 |
7 |
8 | ,
9 | document.getElementById("root")
10 | );
11 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | let modalRoot;
5 |
6 | const Modal = ({ children }) => {
7 | modalRoot = modalRoot ? modalRoot : document.getElementById("modal");
8 |
9 | const elRef = useRef(null);
10 | if (!elRef.current) {
11 | elRef.current = document.createElement("div");
12 | }
13 |
14 | useEffect(() => {
15 | modalRoot.appendChild(elRef.current);
16 | return () => modalRoot.removeChild(elRef.current);
17 | }, []);
18 |
19 | return createPortal({children}
, elRef.current);
20 | };
21 |
22 | export default Modal;
23 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/server-side-rendering-2/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "env": {
12 | "development": {
13 | "plugins": ["@babel/plugin-proposal-class-properties"]
14 | },
15 | "production": {
16 | "plugins": ["@babel/plugin-proposal-class-properties"]
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/server-side-rendering-2/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server-side-rendering-2/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | dist-server/
5 | .env
6 | .DS_Store
7 | coverage/
8 | .vscode/
9 |
--------------------------------------------------------------------------------
/server-side-rendering-2/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/server-side-rendering-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html",
10 | "build:client": "parcel build --public-url ./dist/ src/index.html",
11 | "build:server": "parcel build -d dist-server --target node server/index.js",
12 | "build": "npm run build:client && npm run build:server",
13 | "start": "npm -s run build && node dist-server/index.js"
14 | },
15 | "author": "Brian Holt ",
16 | "license": "Apache-2.0",
17 | "devDependencies": {
18 | "@babel/cli": "7.13.0",
19 | "@babel/core": "7.12.16",
20 | "@babel/eslint-parser": "7.13.4",
21 | "@babel/plugin-proposal-class-properties": "7.13.0",
22 | "@babel/preset-env": "7.13.5",
23 | "@babel/preset-react": "7.12.13",
24 | "eslint": "7.18.0",
25 | "eslint-config-prettier": "8.1.0",
26 | "eslint-plugin-import": "2.22.1",
27 | "eslint-plugin-jsx-a11y": "6.4.1",
28 | "eslint-plugin-react": "7.22.0",
29 | "eslint-plugin-react-hooks": "4.2.0",
30 | "parcel": "1.12.3",
31 | "prettier": "2.2.1"
32 | },
33 | "dependencies": {
34 | "express": "4.17.1",
35 | "react": "17.0.1",
36 | "react-dom": "17.0.1",
37 | "react-router-dom": "5.2.0"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/server-side-rendering-2/server/index.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { renderToNodeStream } from "react-dom/server";
3 | import { StaticRouter } from "react-router-dom";
4 | import fs from "fs";
5 | import App from "../src/App";
6 |
7 | const PORT = process.env.PORT || 3000;
8 |
9 | const html = fs.readFileSync("dist/index.html").toString();
10 |
11 | const parts = html.split("not rendered");
12 |
13 | const app = express();
14 |
15 | app.use("/dist", express.static("dist"));
16 | app.use((req, res) => {
17 | res.write(parts[0]);
18 | const staticContext = {};
19 | const reactMarkup = (
20 |
21 |
22 |
23 | );
24 |
25 | const stream = renderToNodeStream(reactMarkup);
26 | stream.pipe(res, { end: false });
27 | stream.on("end", () => {
28 | res.status(staticContext.statusCode || 200);
29 | res.write(parts[1]);
30 | res.end();
31 | });
32 | });
33 |
34 | console.log(`listening on http://localhost:${PORT}`);
35 | app.listen(PORT);
36 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { Route, Switch, Link } from "react-router-dom";
3 | import Details from "./Details";
4 | import SearchParams from "./SearchParams";
5 | import ThemeContext from "./ThemeContext";
6 |
7 | const App = () => {
8 | const theme = useState("darkblue");
9 | return (
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default App;
31 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/ClientApp.js:
--------------------------------------------------------------------------------
1 | import { hydrate } from "react-dom";
2 | import { BrowserRouter, BrowserRouter as Router } from "react-router-dom";
3 | import App from "./App";
4 |
5 | hydrate(
6 |
7 |
8 | ,
9 | document.getElementById("root")
10 | );
11 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | let modalRoot;
5 |
6 | const Modal = ({ children }) => {
7 | modalRoot = modalRoot ? modalRoot : document.getElementById("modal");
8 |
9 | const elRef = useRef(null);
10 | if (!elRef.current) {
11 | elRef.current = document.createElement("div");
12 | }
13 |
14 | useEffect(() => {
15 | modalRoot.appendChild(elRef.current);
16 | return () => modalRoot.removeChild(elRef.current);
17 | }, []);
18 |
19 | return createPortal({children}
, elRef.current);
20 | };
21 |
22 | export default Modal;
23 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/tailwind/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/tailwind/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tailwind/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/tailwind/.postcssrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": {
3 | "autoprefixer": {},
4 | "tailwindcss": {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tailwind/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/tailwind/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/eslint-parser": "7.13.4",
16 | "@babel/plugin-proposal-class-properties": "7.13.0",
17 | "@babel/preset-env": "7.13.5",
18 | "@babel/preset-react": "7.12.13",
19 | "@tailwindcss/forms": "0.2.1",
20 | "@tailwindcss/postcss7-compat": "2.0.3",
21 | "autoprefixer": "9.8.6",
22 | "eslint": "7.18.0",
23 | "eslint-config-prettier": "8.1.0",
24 | "eslint-plugin-import": "2.22.1",
25 | "eslint-plugin-jsx-a11y": "6.4.1",
26 | "eslint-plugin-react": "7.22.0",
27 | "eslint-plugin-react-hooks": "4.2.0",
28 | "parcel": "1.12.3",
29 | "postcss": "7.0.35",
30 | "prettier": "2.2.1",
31 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@2.0.3"
32 | },
33 | "dependencies": {
34 | "react": "17.0.1",
35 | "react-dom": "17.0.1",
36 | "react-router-dom": "5.2.0"
37 | },
38 | "browserslist": [
39 | "last 2 Chrome versions"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/tailwind/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
19 |
20 |
21 |
22 | Adopt Me!
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | render(
40 |
41 |
42 | ,
43 | document.getElementById("root")
44 | );
45 |
--------------------------------------------------------------------------------
/tailwind/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/tailwind/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/tailwind/src/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal = ({ children }) => {
7 | const elRef = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | modalRoot.appendChild(elRef.current);
14 | return () => modalRoot.removeChild(elRef.current);
15 | }, []);
16 |
17 | return createPortal({children}
, elRef.current);
18 | };
19 |
20 | export default Modal;
21 |
--------------------------------------------------------------------------------
/tailwind/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/tailwind/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/tailwind/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/tailwind/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Adopt Me
9 |
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tailwind/src/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer components {
6 | .search-label {
7 | margin-bottom: 15px;
8 | padding-top: 15px;
9 | width: 100%;
10 | text-align: center;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: center;
15 | }
16 |
17 | .search-control {
18 | padding: 8px 15px;
19 | width: 300px;
20 | border-radius: 5px;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tailwind/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/tailwind/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: [],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {
8 | opacity: ({ after }) => after(["disabled"]),
9 | },
10 | plugins: [require("@tailwindcss/forms")],
11 | };
12 |
--------------------------------------------------------------------------------
/testing/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"],
12 | "env": {
13 | "test": {
14 | "presets": [
15 | [
16 | "@babel/preset-env",
17 | {
18 | "targets": {
19 | "node": "current"
20 | }
21 | }
22 | ]
23 | ]
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/testing/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "prettier"
9 | ],
10 | "rules": {
11 | "react/prop-types": 0,
12 | "react/react-in-jsx-scope": 0
13 | },
14 | "plugins": ["react", "import", "jsx-a11y"],
15 | "parser": "@babel/eslint-parser",
16 | "parserOptions": {
17 | "ecmaVersion": 2020,
18 | "sourceType": "module",
19 | "ecmaFeatures": {
20 | "jsx": true
21 | }
22 | },
23 | "env": {
24 | "es6": true,
25 | "browser": true,
26 | "node": true
27 | },
28 | "settings": {
29 | "react": {
30 | "version": "detect"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/testing/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 | coverage/
9 |
--------------------------------------------------------------------------------
/testing/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/testing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
9 | "dev": "parcel src/index.html",
10 | "test": "jest",
11 | "test:watch": "jest --watch",
12 | "test:coverage": "jest --coverage"
13 | },
14 | "author": "Brian Holt ",
15 | "license": "Apache-2.0",
16 | "jest": {
17 | "setupFiles": [
18 | "./src/setupJest.js"
19 | ]
20 | },
21 | "devDependencies": {
22 | "@babel/core": "7.12.16",
23 | "@babel/eslint-parser": "7.13.4",
24 | "@babel/plugin-proposal-class-properties": "7.13.0",
25 | "@babel/preset-env": "7.13.5",
26 | "@babel/preset-react": "7.12.13",
27 | "@testing-library/react": "11.2.5",
28 | "@testing-library/react-hooks": "5.1.0",
29 | "eslint": "7.18.0",
30 | "eslint-config-prettier": "8.1.0",
31 | "eslint-plugin-import": "2.22.1",
32 | "eslint-plugin-jsx-a11y": "6.4.1",
33 | "eslint-plugin-react": "7.22.0",
34 | "eslint-plugin-react-hooks": "4.2.0",
35 | "jest": "26.6.3",
36 | "jest-fetch-mock": "3.0.3",
37 | "parcel": "1.12.3",
38 | "prettier": "2.2.1",
39 | "react-test-renderer": "17.0.1"
40 | },
41 | "dependencies": {
42 | "react": "17.0.1",
43 | "react-dom": "17.0.1",
44 | "react-router-dom": "5.2.0"
45 | },
46 | "browserslist": [
47 | "last 2 Chrome versions"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/testing/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/testing/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

36 | ))}
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 | export default Carousel;
44 |
--------------------------------------------------------------------------------
/testing/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/testing/src/Modal.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal = ({ children }) => {
7 | const elRef = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | modalRoot.appendChild(elRef.current);
14 | return () => modalRoot.removeChild(elRef.current);
15 | }, []);
16 |
17 | return createPortal({children}
, elRef.current);
18 | };
19 |
20 | export default Modal;
21 |
--------------------------------------------------------------------------------
/testing/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images && images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/testing/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/testing/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/testing/src/__tests__/Carousel.test.js:
--------------------------------------------------------------------------------
1 | import { expect, test } from "@jest/globals";
2 | import { render } from "@testing-library/react";
3 | import Carousel from "../Carousel.js";
4 |
5 | test("lets users click on thumbnails to make them the hero", async () => {
6 | const images = ["0.jpg", "1.jpg", "2.jpg", "3.jpg"];
7 | const carousel = render();
8 |
9 | const hero = await carousel.findByTestId("hero");
10 | expect(hero.src).toContain(images[0]);
11 |
12 | for (let i = 0; i < images.length; i++) {
13 | const image = images[i];
14 |
15 | const thumb = await carousel.findByTestId(`thumbnail${i}`);
16 | thumb.click();
17 |
18 | expect(hero.src).toContain(image);
19 | expect(thumb.classList).toContain("active");
20 | }
21 | });
22 |
--------------------------------------------------------------------------------
/testing/src/__tests__/Pet.test.js:
--------------------------------------------------------------------------------
1 | import { expect, test } from "@jest/globals";
2 | import { render } from "@testing-library/react";
3 | import { StaticRouter } from "react-router-dom";
4 | import Pet from "../Pet.js";
5 |
6 | test("displays a default thumbnail", async () => {
7 | const pet = render(
8 |
9 |
10 |
11 | );
12 |
13 | const petThumbnail = await pet.findByTestId("thumbnail");
14 | expect(petThumbnail.src).toContain("none.jpg");
15 | });
16 |
17 | test("displays a non-default thumbnail", async () => {
18 | const pet = render(
19 |
20 |
21 |
22 | );
23 |
24 | const petThumbnail = await pet.findByTestId("thumbnail");
25 | expect(petThumbnail.src).toContain("1.jpg");
26 | });
27 |
--------------------------------------------------------------------------------
/testing/src/__tests__/useBreedList.test.js:
--------------------------------------------------------------------------------
1 | import { expect, test } from "@jest/globals";
2 | // import { render } from "@testing-library/react";
3 | import { renderHook } from "@testing-library/react-hooks";
4 | import useBreedList from "../useBreedList.js";
5 |
6 | // function getBreedList(animal) {
7 | // let list;
8 |
9 | // function TestComponent() {
10 | // list = useBreedList(animal);
11 | // return null;
12 | // }
13 |
14 | // render();
15 |
16 | // return list;
17 | // }
18 |
19 | test("gives an empty list with no animal", async () => {
20 | // const [breedList, status] = getBreedList();
21 |
22 | const { result } = renderHook(() => useBreedList(""));
23 |
24 | const [breedList, status] = result.current;
25 |
26 | expect(breedList).toHaveLength(0);
27 | expect(status).toBe("unloaded");
28 | });
29 |
30 | test("gives back breeds with an animal", async () => {
31 | const breeds = [
32 | "Havanese",
33 | "Bichon Frise",
34 | "Poodle",
35 | "Maltese",
36 | "Golden Retriever",
37 | "Labrador",
38 | "Husky",
39 | ];
40 | fetch.mockResponseOnce(
41 | JSON.stringify({
42 | animal: "dog",
43 | breeds,
44 | })
45 | );
46 | const { result, waitForNextUpdate } = renderHook(() => useBreedList("dog"));
47 |
48 | await waitForNextUpdate();
49 |
50 | const [breedList, status] = result.current;
51 | expect(status).toBe("loaded");
52 | expect(breedList).toEqual(breeds);
53 | });
54 |
--------------------------------------------------------------------------------
/testing/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/testing/src/setupJest.js:
--------------------------------------------------------------------------------
1 | import { enableFetchMocks } from "jest-fetch-mock";
2 |
3 | enableFetchMocks();
4 |
--------------------------------------------------------------------------------
/testing/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/typescript-1/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/typescript-1/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
10 | "prettier"
11 | ],
12 | "rules": {
13 | "react/prop-types": 0,
14 | "react/react-in-jsx-scope": 0,
15 | "@typescript-eslint/no-empty-function": 0
16 | },
17 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
18 | "parser": "@typescript-eslint/parser",
19 | "parserOptions": {
20 | "project": "./tsconfig.json",
21 | "ecmaVersion": 2020,
22 | "sourceType": "module",
23 | "ecmaFeatures": {
24 | "jsx": true
25 | }
26 | },
27 | "env": {
28 | "es6": true,
29 | "browser": true,
30 | "node": true
31 | },
32 | "settings": {
33 | "react": {
34 | "version": "detect"
35 | },
36 | "import/parsers": {
37 | "@typescript-eslint/parser": [".ts", ".tsx"]
38 | },
39 | "import/resolver": {
40 | "typescript": {
41 | "alwaysTryTypes": true
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/typescript-1/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/typescript-1/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/typescript-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/plugin-proposal-class-properties": "7.13.0",
16 | "@babel/preset-env": "7.13.5",
17 | "@babel/preset-react": "7.12.13",
18 | "@types/react": "17.0.2",
19 | "@types/react-dom": "17.0.1",
20 | "@types/react-router-dom": "5.1.7",
21 | "@typescript-eslint/eslint-plugin": "4.16.1",
22 | "@typescript-eslint/parser": "^4.16.1",
23 | "eslint": "7.18.0",
24 | "eslint-config-prettier": "8.1.0",
25 | "eslint-import-resolver-typescript": "2.4.0",
26 | "eslint-plugin-import": "^2.22.1",
27 | "eslint-plugin-jsx-a11y": "6.4.1",
28 | "eslint-plugin-react": "7.22.0",
29 | "eslint-plugin-react-hooks": "4.2.0",
30 | "parcel": "1.12.3",
31 | "prettier": "2.2.1",
32 | "typescript": "4.2.2"
33 | },
34 | "dependencies": {
35 | "react": "17.0.1",
36 | "react-dom": "17.0.1",
37 | "react-router-dom": "5.2.0"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/typescript-1/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/typescript-1/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/typescript-1/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/typescript-1/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, ReactNode, MutableRefObject } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal = ({ children }: { children: ReactNode }) => {
7 | const elRef: MutableRefObject = useRef(null);
8 | if (!elRef.current) {
9 | const x = document.createElement("div")
10 | elRef.current = document.createElement("div");
11 | }
12 |
13 | useEffect(() => {
14 | if (!modalRoot || !elRef.current) {
15 | return;
16 | }
17 | modalRoot.appendChild(elRef.current);
18 | return () => {
19 | if (elRef.current) {
20 | modalRoot.removeChild(elRef.current);
21 | }
22 | };
23 | }, []);
24 |
25 | return createPortal({children}
, elRef.current);
26 | };
27 |
28 | export default Modal;
29 |
--------------------------------------------------------------------------------
/typescript-1/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/typescript-1/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/typescript-1/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/typescript-1/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/typescript-1/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/typescript-2/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/typescript-2/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
10 | "prettier"
11 | ],
12 | "rules": {
13 | "react/prop-types": 0,
14 | "react/react-in-jsx-scope": 0,
15 | "@typescript-eslint/no-empty-function": 0
16 | },
17 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
18 | "parser": "@typescript-eslint/parser",
19 | "parserOptions": {
20 | "project": "./tsconfig.json",
21 | "ecmaVersion": 2020,
22 | "sourceType": "module",
23 | "ecmaFeatures": {
24 | "jsx": true
25 | }
26 | },
27 | "env": {
28 | "es6": true,
29 | "browser": true,
30 | "node": true
31 | },
32 | "settings": {
33 | "react": {
34 | "version": "detect"
35 | },
36 | "import/parsers": {
37 | "@typescript-eslint/parser": [".ts", ".tsx"]
38 | },
39 | "import/resolver": {
40 | "typescript": {
41 | "alwaysTryTypes": true
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/typescript-2/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/typescript-2/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/typescript-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/plugin-proposal-class-properties": "7.13.0",
16 | "@babel/preset-env": "7.13.5",
17 | "@babel/preset-react": "7.12.13",
18 | "@types/react": "17.0.2",
19 | "@types/react-dom": "17.0.1",
20 | "@types/react-router-dom": "5.1.7",
21 | "@typescript-eslint/eslint-plugin": "4.16.1",
22 | "@typescript-eslint/parser": "^4.16.1",
23 | "eslint": "7.18.0",
24 | "eslint-config-prettier": "8.1.0",
25 | "eslint-import-resolver-typescript": "2.4.0",
26 | "eslint-plugin-import": "^2.22.1",
27 | "eslint-plugin-jsx-a11y": "6.4.1",
28 | "eslint-plugin-react": "7.22.0",
29 | "eslint-plugin-react-hooks": "4.2.0",
30 | "parcel": "1.12.3",
31 | "prettier": "2.2.1",
32 | "typescript": "4.2.2"
33 | },
34 | "dependencies": {
35 | "react": "17.0.1",
36 | "react-dom": "17.0.1",
37 | "react-router-dom": "5.2.0"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/typescript-2/src/APIResponsesTypes.ts:
--------------------------------------------------------------------------------
1 | export interface Pet {
2 | id: number;
3 | name: string;
4 | animal: "dog" | "cat" | "bird" | "reptile" | "rabbit";
5 | description: string;
6 | breed: string;
7 | images: string[];
8 | }
9 |
10 | export interface PetAPIResponse {
11 | numberOfResults: number;
12 | startIndex: number;
13 | endIndex: number;
14 | hasNext: boolean;
15 | pets: Pet[];
16 | }
17 |
--------------------------------------------------------------------------------
/typescript-2/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/typescript-2/src/Carousel.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 |
3 | class Carousel extends Component {
4 | state = {
5 | active: 0,
6 | };
7 |
8 | static defaultProps = {
9 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
10 | };
11 |
12 | handleIndexClick = (event) => {
13 | this.setState({
14 | active: +event.target.dataset.index,
15 | });
16 | };
17 |
18 | render() {
19 | const { active } = this.state;
20 | const { images } = this.props;
21 | return (
22 |
23 |

24 |
25 | {images.map((photo, index) => (
26 | // eslint-disable-next-line
27 |

35 | ))}
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default Carousel;
43 |
--------------------------------------------------------------------------------
/typescript-2/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true, redirect: false };
9 | }
10 | componentDidCatch(error, info) {
11 | console.error("ErrorBoundary caught an error", error, info);
12 | }
13 | componentDidUpdate() {
14 | if (this.state.hasError) {
15 | setTimeout(() => this.setState({ redirect: true }), 5000);
16 | }
17 | }
18 |
19 | render() {
20 | if (this.state.redirect) {
21 | return ;
22 | } else if (this.state.hasError) {
23 | return (
24 |
25 | There was an error with this listing. Click here{" "}
26 | to back to the home page or wait five seconds.
27 |
28 | );
29 | }
30 |
31 | return this.props.children;
32 | }
33 | }
34 |
35 | export default ErrorBoundary;
36 |
--------------------------------------------------------------------------------
/typescript-2/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, ReactNode, MutableRefObject } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal = ({ children }: { children: ReactNode }) => {
7 | const elRef: MutableRefObject = useRef(null);
8 | if (!elRef.current) {
9 | const x = document.createElement("div")
10 | elRef.current = document.createElement("div");
11 | }
12 |
13 | useEffect(() => {
14 | if (!modalRoot || !elRef.current) {
15 | return;
16 | }
17 | modalRoot.appendChild(elRef.current);
18 | return () => {
19 | if (elRef.current) {
20 | modalRoot.removeChild(elRef.current);
21 | }
22 | };
23 | }, []);
24 |
25 | return createPortal({children}
, elRef.current);
26 | };
27 |
28 | export default Modal;
29 |
--------------------------------------------------------------------------------
/typescript-2/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/typescript-2/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/typescript-2/src/ThemeContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext<[string, (theme: string) => void]>([
4 | "green",
5 | () => {},
6 | ]);
7 |
8 | export default ThemeContext;
9 |
--------------------------------------------------------------------------------
/typescript-2/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/typescript-2/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/typescript-3/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/typescript-3/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
10 | "prettier"
11 | ],
12 | "rules": {
13 | "react/prop-types": 0,
14 | "react/react-in-jsx-scope": 0,
15 | "@typescript-eslint/no-empty-function": 0
16 | },
17 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
18 | "parser": "@typescript-eslint/parser",
19 | "parserOptions": {
20 | "project": "./tsconfig.json",
21 | "ecmaVersion": 2020,
22 | "sourceType": "module",
23 | "ecmaFeatures": {
24 | "jsx": true
25 | }
26 | },
27 | "env": {
28 | "es6": true,
29 | "browser": true,
30 | "node": true
31 | },
32 | "settings": {
33 | "react": {
34 | "version": "detect"
35 | },
36 | "import/parsers": {
37 | "@typescript-eslint/parser": [".ts", ".tsx"]
38 | },
39 | "import/resolver": {
40 | "typescript": {
41 | "alwaysTryTypes": true
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/typescript-3/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/typescript-3/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/typescript-3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/plugin-proposal-class-properties": "7.13.0",
16 | "@babel/preset-env": "7.13.5",
17 | "@babel/preset-react": "7.12.13",
18 | "@types/react": "17.0.2",
19 | "@types/react-dom": "17.0.1",
20 | "@types/react-router-dom": "5.1.7",
21 | "@typescript-eslint/eslint-plugin": "4.16.1",
22 | "@typescript-eslint/parser": "^4.16.1",
23 | "eslint": "7.18.0",
24 | "eslint-config-prettier": "8.1.0",
25 | "eslint-import-resolver-typescript": "2.4.0",
26 | "eslint-plugin-import": "^2.22.1",
27 | "eslint-plugin-jsx-a11y": "6.4.1",
28 | "eslint-plugin-react": "7.22.0",
29 | "eslint-plugin-react-hooks": "4.2.0",
30 | "parcel": "1.12.3",
31 | "prettier": "2.2.1",
32 | "typescript": "4.2.2"
33 | },
34 | "dependencies": {
35 | "react": "17.0.1",
36 | "react-dom": "17.0.1",
37 | "react-router-dom": "5.2.0"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/typescript-3/src/APIResponsesTypes.ts:
--------------------------------------------------------------------------------
1 | export interface Pet {
2 | id: number;
3 | name: string;
4 | animal: "dog" | "cat" | "bird" | "reptile" | "rabbit";
5 | description: string;
6 | breed: string;
7 | images: string[];
8 | }
9 |
10 | export interface PetAPIResponse {
11 | numberOfResults: number;
12 | startIndex: number;
13 | endIndex: number;
14 | hasNext: boolean;
15 | pets: Pet[];
16 | }
17 |
--------------------------------------------------------------------------------
/typescript-3/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/typescript-3/src/Carousel.tsx:
--------------------------------------------------------------------------------
1 | import { Component, MouseEvent, ReactNode } from "react";
2 |
3 | interface IProps {
4 | images: string[];
5 | }
6 |
7 | class Carousel extends Component {
8 | state = {
9 | active: 0,
10 | };
11 |
12 | static defaultProps = {
13 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
14 | };
15 |
16 | handleIndexClick = (event: MouseEvent): void => {
17 | if (!(event.target instanceof HTMLElement)) {
18 | return;
19 | }
20 |
21 | if (event.target.dataset.index) {
22 | this.setState({
23 | active: +event.target.dataset.index,
24 | });
25 | }
26 | };
27 |
28 | render(): ReactNode {
29 | const { active } = this.state;
30 | const { images } = this.props;
31 | return (
32 |
33 |

34 |
35 | {images.map((photo, index) => (
36 | // eslint-disable-next-line
37 |

45 | ))}
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default Carousel;
53 |
--------------------------------------------------------------------------------
/typescript-3/src/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component, ErrorInfo, ReactNode } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = {
7 | redirect: "",
8 | hasError: false,
9 | };
10 | static getDerivedStateFromError(): { hasError: boolean; redirect: boolean } {
11 | return { hasError: true, redirect: false };
12 | }
13 | componentDidCatch(error: Error, info: ErrorInfo): void {
14 | console.error("ErrorBoundary caught an error", error, info);
15 | }
16 | componentDidUpdate(): void {
17 | if (this.state.hasError) {
18 | setTimeout(() => this.setState({ redirect: true }), 5000);
19 | }
20 | }
21 | render(): ReactNode {
22 | if (this.state.redirect) {
23 | return ;
24 | } else if (this.state.hasError) {
25 | return (
26 |
27 | There was an error with this listing. Click here{" "}
28 | to back to the home page or wait five seconds.
29 |
30 | );
31 | }
32 |
33 | return this.props.children;
34 | }
35 | }
36 |
37 | export default ErrorBoundary;
38 |
--------------------------------------------------------------------------------
/typescript-3/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, MutableRefObject, FunctionComponent } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal: FunctionComponent = ({ children }) => {
7 | const elRef: MutableRefObject = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | if (!modalRoot || !elRef.current) {
14 | return;
15 | }
16 | modalRoot.appendChild(elRef.current);
17 | return () => {
18 | if (elRef.current) {
19 | modalRoot.removeChild(elRef.current);
20 | }
21 | };
22 | }, []);
23 |
24 | return createPortal({children}
, elRef.current);
25 | };
26 |
27 | export default Modal;
28 |
--------------------------------------------------------------------------------
/typescript-3/src/Pet.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | const Pet = (props) => {
4 | const { name, animal, breed, images, location, id } = props;
5 |
6 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
7 | if (images.length) {
8 | hero = images[0];
9 | }
10 |
11 | return (
12 |
13 |
14 |

15 |
16 |
17 |
{name}
18 | {`${animal} — ${breed} — ${location}`}
19 |
20 |
21 | );
22 | };
23 |
24 | export default Pet;
25 |
--------------------------------------------------------------------------------
/typescript-3/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/typescript-3/src/ThemeContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext<[string, (theme: string) => void]>([
4 | "green",
5 | () => {},
6 | ]);
7 |
8 | export default ThemeContext;
9 |
--------------------------------------------------------------------------------
/typescript-3/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/typescript-3/src/useBreedList.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const localCache = {};
4 |
5 | export default function useBreedList(animal) {
6 | const [breedList, setBreedList] = useState([]);
7 | const [status, setStatus] = useState("unloaded");
8 |
9 | useEffect(() => {
10 | if (!animal) {
11 | setBreedList([]);
12 | } else if (localCache[animal]) {
13 | setBreedList(localCache[animal]);
14 | } else {
15 | requestBreedList();
16 | }
17 |
18 | async function requestBreedList() {
19 | setBreedList([]);
20 | setStatus("loading");
21 | const res = await fetch(
22 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
23 | );
24 | const json = await res.json();
25 | localCache[animal] = json.breeds || [];
26 | setBreedList(localCache[animal]);
27 | setStatus("loaded");
28 | }
29 | }, [animal]);
30 |
31 | return [breedList, status];
32 | }
33 |
--------------------------------------------------------------------------------
/typescript-4/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/typescript-4/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
10 | "prettier"
11 | ],
12 | "rules": {
13 | "react/prop-types": 0,
14 | "react/react-in-jsx-scope": 0,
15 | "@typescript-eslint/no-empty-function": 0
16 | },
17 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
18 | "parser": "@typescript-eslint/parser",
19 | "parserOptions": {
20 | "project": "./tsconfig.json",
21 | "ecmaVersion": 2020,
22 | "sourceType": "module",
23 | "ecmaFeatures": {
24 | "jsx": true
25 | }
26 | },
27 | "env": {
28 | "es6": true,
29 | "browser": true,
30 | "node": true
31 | },
32 | "settings": {
33 | "react": {
34 | "version": "detect"
35 | },
36 | "import/parsers": {
37 | "@typescript-eslint/parser": [".ts", ".tsx"]
38 | },
39 | "import/resolver": {
40 | "typescript": {
41 | "alwaysTryTypes": true
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/typescript-4/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/typescript-4/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/typescript-4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/plugin-proposal-class-properties": "7.13.0",
16 | "@babel/preset-env": "7.13.5",
17 | "@babel/preset-react": "7.12.13",
18 | "@types/react": "17.0.2",
19 | "@types/react-dom": "17.0.1",
20 | "@types/react-router-dom": "5.1.7",
21 | "@typescript-eslint/eslint-plugin": "4.16.1",
22 | "@typescript-eslint/parser": "^4.16.1",
23 | "eslint": "7.18.0",
24 | "eslint-config-prettier": "8.1.0",
25 | "eslint-import-resolver-typescript": "2.4.0",
26 | "eslint-plugin-import": "^2.22.1",
27 | "eslint-plugin-jsx-a11y": "6.4.1",
28 | "eslint-plugin-react": "7.22.0",
29 | "eslint-plugin-react-hooks": "4.2.0",
30 | "parcel": "1.12.3",
31 | "prettier": "2.2.1",
32 | "typescript": "4.2.2"
33 | },
34 | "dependencies": {
35 | "react": "17.0.1",
36 | "react-dom": "17.0.1",
37 | "react-router-dom": "5.2.0"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/typescript-4/src/APIResponsesTypes.ts:
--------------------------------------------------------------------------------
1 | export type Animal = "dog" | "cat" | "bird" | "reptile" | "rabbit";
2 |
3 | export interface Pet {
4 | id: number;
5 | name: string;
6 | animal: Animal;
7 | description: string;
8 | breed: string;
9 | images: string[];
10 | }
11 |
12 | export interface PetAPIResponse {
13 | numberOfResults: number;
14 | startIndex: number;
15 | endIndex: number;
16 | hasNext: boolean;
17 | pets: Pet[];
18 | }
19 |
20 | export interface BreedListAPIResponse {
21 | animal: Animal;
22 | breeds: string[];
23 | }
24 |
--------------------------------------------------------------------------------
/typescript-4/src/App.js:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/typescript-4/src/Carousel.tsx:
--------------------------------------------------------------------------------
1 | import { Component, MouseEvent, ReactNode } from "react";
2 |
3 | interface Props {
4 | images: string[];
5 | }
6 |
7 | class Carousel extends Component {
8 | state = {
9 | active: 0,
10 | };
11 |
12 | static defaultProps = {
13 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
14 | };
15 |
16 | handleIndexClick = (event: MouseEvent): void => {
17 | if (!(event.target instanceof HTMLElement)) {
18 | return;
19 | }
20 |
21 | if (event.target.dataset.index) {
22 | this.setState({
23 | active: +event.target.dataset.index,
24 | });
25 | }
26 | };
27 |
28 | render(): ReactNode {
29 | const { active } = this.state;
30 | const { images } = this.props;
31 | return (
32 |
33 |

34 |
35 | {images.map((photo, index) => (
36 | // eslint-disable-next-line
37 |

45 | ))}
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default Carousel;
53 |
--------------------------------------------------------------------------------
/typescript-4/src/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component, ErrorInfo, ReactNode } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = {
7 | redirect: "",
8 | hasError: false,
9 | };
10 | static getDerivedStateFromError(): { hasError: boolean; redirect: boolean } {
11 | return { hasError: true, redirect: false };
12 | }
13 | componentDidCatch(error: Error, info: ErrorInfo): void {
14 | console.error("ErrorBoundary caught an error", error, info);
15 | }
16 | componentDidUpdate(): void {
17 | if (this.state.hasError) {
18 | setTimeout(() => this.setState({ redirect: true }), 5000);
19 | }
20 | }
21 | render(): ReactNode {
22 | if (this.state.redirect) {
23 | return ;
24 | } else if (this.state.hasError) {
25 | return (
26 |
27 | There was an error with this listing. Click here{" "}
28 | to back to the home page or wait five seconds.
29 |
30 | );
31 | }
32 |
33 | return this.props.children;
34 | }
35 | }
36 |
37 | export default ErrorBoundary;
38 |
--------------------------------------------------------------------------------
/typescript-4/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, MutableRefObject, FunctionComponent } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal: FunctionComponent = ({ children }) => {
7 | const elRef: MutableRefObject = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | if (!modalRoot || !elRef.current) {
14 | return;
15 | }
16 | modalRoot.appendChild(elRef.current);
17 | return () => {
18 | if (elRef.current) {
19 | modalRoot.removeChild(elRef.current);
20 | }
21 | };
22 | }, []);
23 |
24 | return createPortal({children}
, elRef.current);
25 | };
26 |
27 | export default Modal;
28 |
--------------------------------------------------------------------------------
/typescript-4/src/Pet.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | interface Props {
5 | name: string;
6 | animal: string;
7 | breed: string;
8 | images: string[];
9 | location: string;
10 | id: number;
11 | }
12 |
13 | const Pet: FunctionComponent = (props) => {
14 | const { name, animal, breed, images, location, id } = props;
15 |
16 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
17 | if (images.length) {
18 | hero = images[0];
19 | }
20 |
21 | return (
22 |
23 |
24 |

25 |
26 |
27 |
{name}
28 | {`${animal} — ${breed} — ${location}`}
29 |
30 |
31 | );
32 | };
33 |
34 | export default Pet;
35 |
--------------------------------------------------------------------------------
/typescript-4/src/Results.js:
--------------------------------------------------------------------------------
1 | import Pet from "./Pet";
2 |
3 | const Results = ({ pets }) => {
4 | return (
5 |
6 | {!pets.length ? (
7 |
No Pets Found
8 | ) : (
9 | pets.map((pet) => {
10 | return (
11 |
20 | );
21 | })
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default Results;
28 |
--------------------------------------------------------------------------------
/typescript-4/src/ThemeContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext<[string, (theme: string) => void]>([
4 | "green",
5 | () => {},
6 | ]);
7 |
8 | export default ThemeContext;
9 |
--------------------------------------------------------------------------------
/typescript-4/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/typescript-4/src/useBreedList.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { Animal, BreedListAPIResponse } from "./APIResponsesTypes";
3 |
4 | const localCache: {
5 | [index: string]: string[];
6 | } = {};
7 |
8 | type Status = "unloaded" | "loading" | "loaded";
9 |
10 | export default function useBreedList(animal: Animal): [string[], Status] {
11 | const [breedList, setBreedList] = useState([] as string[]);
12 | const [status, setStatus] = useState("unloaded" as Status);
13 |
14 | useEffect(() => {
15 | if (!animal) {
16 | setBreedList([]);
17 | } else if (localCache[animal]) {
18 | setBreedList(localCache[animal]);
19 | } else {
20 | void requestBreedList();
21 | }
22 |
23 | async function requestBreedList() {
24 | setBreedList([]);
25 | setStatus("loading");
26 | const res = await fetch(
27 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
28 | );
29 | const json = (await res.json()) as BreedListAPIResponse;
30 | localCache[animal] = json.breeds || [];
31 | setBreedList(localCache[animal]);
32 | setStatus("loaded");
33 | }
34 | }, [animal]);
35 |
36 | return [breedList, status];
37 | }
38 |
--------------------------------------------------------------------------------
/typescript-5/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime": "automatic"
7 | }
8 | ],
9 | "@babel/preset-env"
10 | ],
11 | "plugins": ["@babel/plugin-proposal-class-properties"]
12 | }
--------------------------------------------------------------------------------
/typescript-5/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:import/errors",
5 | "plugin:react/recommended",
6 | "plugin:jsx-a11y/recommended",
7 | "plugin:react-hooks/recommended",
8 | "plugin:@typescript-eslint/recommended",
9 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
10 | "prettier"
11 | ],
12 | "rules": {
13 | "react/prop-types": 0,
14 | "react/react-in-jsx-scope": 0,
15 | "@typescript-eslint/no-empty-function": 0
16 | },
17 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
18 | "parser": "@typescript-eslint/parser",
19 | "parserOptions": {
20 | "project": "./tsconfig.json",
21 | "ecmaVersion": 2020,
22 | "sourceType": "module",
23 | "ecmaFeatures": {
24 | "jsx": true
25 | }
26 | },
27 | "env": {
28 | "es6": true,
29 | "browser": true,
30 | "node": true
31 | },
32 | "settings": {
33 | "react": {
34 | "version": "detect"
35 | },
36 | "import/parsers": {
37 | "@typescript-eslint/parser": [".ts", ".tsx"]
38 | },
39 | "import/resolver": {
40 | "typescript": {
41 | "alwaysTryTypes": true
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/typescript-5/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
--------------------------------------------------------------------------------
/typescript-5/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/typescript-5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "1.0.0",
4 | "description": "The Adopt Me pet adoption app",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
8 | "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\" --quiet",
9 | "dev": "parcel src/index.html"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/core": "7.12.16",
15 | "@babel/plugin-proposal-class-properties": "7.13.0",
16 | "@babel/preset-env": "7.13.5",
17 | "@babel/preset-react": "7.12.13",
18 | "@types/react": "17.0.2",
19 | "@types/react-dom": "17.0.1",
20 | "@types/react-router-dom": "5.1.7",
21 | "@typescript-eslint/eslint-plugin": "4.16.1",
22 | "@typescript-eslint/parser": "^4.16.1",
23 | "eslint": "7.18.0",
24 | "eslint-config-prettier": "8.1.0",
25 | "eslint-import-resolver-typescript": "2.4.0",
26 | "eslint-plugin-import": "^2.22.1",
27 | "eslint-plugin-jsx-a11y": "6.4.1",
28 | "eslint-plugin-react": "7.22.0",
29 | "eslint-plugin-react-hooks": "4.2.0",
30 | "parcel": "1.12.3",
31 | "prettier": "2.2.1",
32 | "typescript": "4.2.2"
33 | },
34 | "dependencies": {
35 | "react": "17.0.1",
36 | "react-dom": "17.0.1",
37 | "react-router-dom": "5.2.0"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/typescript-5/src/APIResponsesTypes.ts:
--------------------------------------------------------------------------------
1 | export type Animal = "dog" | "cat" | "bird" | "reptile" | "rabbit";
2 |
3 | export interface Pet {
4 | id: number;
5 | name: string;
6 | animal: Animal;
7 | description: string;
8 | breed: string;
9 | images: string[];
10 | city: string;
11 | state: string;
12 | }
13 |
14 | export interface PetAPIResponse {
15 | numberOfResults: number;
16 | startIndex: number;
17 | endIndex: number;
18 | hasNext: boolean;
19 | pets: Pet[];
20 | }
21 |
22 | export interface BreedListAPIResponse {
23 | animal: Animal;
24 | breeds: string[];
25 | }
26 |
--------------------------------------------------------------------------------
/typescript-5/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState, StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { BrowserRouter as Router, Route, Switch, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import SearchParams from "./SearchParams";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/typescript-5/src/Carousel.tsx:
--------------------------------------------------------------------------------
1 | import { Component, MouseEvent, ReactNode } from "react";
2 |
3 | interface Props {
4 | images: string[];
5 | }
6 |
7 | class Carousel extends Component {
8 | state = {
9 | active: 0,
10 | };
11 |
12 | static defaultProps = {
13 | images: ["http://pets-images.dev-apis.com/pets/none.jpg"],
14 | };
15 |
16 | handleIndexClick = (event: MouseEvent): void => {
17 | if (!(event.target instanceof HTMLElement)) {
18 | return;
19 | }
20 |
21 | if (event.target.dataset.index) {
22 | this.setState({
23 | active: +event.target.dataset.index,
24 | });
25 | }
26 | };
27 |
28 | render(): ReactNode {
29 | const { active } = this.state;
30 | const { images } = this.props;
31 | return (
32 |
33 |

34 |
35 | {images.map((photo, index) => (
36 | // eslint-disable-next-line
37 |

45 | ))}
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default Carousel;
53 |
--------------------------------------------------------------------------------
/typescript-5/src/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component, ErrorInfo, ReactNode } from "react";
3 | import { Link, Redirect } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = {
7 | redirect: "",
8 | hasError: false,
9 | };
10 | static getDerivedStateFromError(): { hasError: boolean; redirect: boolean } {
11 | return { hasError: true, redirect: false };
12 | }
13 | componentDidCatch(error: Error, info: ErrorInfo): void {
14 | console.error("ErrorBoundary caught an error", error, info);
15 | }
16 | componentDidUpdate(): void {
17 | if (this.state.hasError) {
18 | setTimeout(() => this.setState({ redirect: true }), 5000);
19 | }
20 | }
21 | render(): ReactNode {
22 | if (this.state.redirect) {
23 | return ;
24 | } else if (this.state.hasError) {
25 | return (
26 |
27 | There was an error with this listing. Click here{" "}
28 | to back to the home page or wait five seconds.
29 |
30 | );
31 | }
32 |
33 | return this.props.children;
34 | }
35 | }
36 |
37 | export default ErrorBoundary;
38 |
--------------------------------------------------------------------------------
/typescript-5/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, MutableRefObject, FunctionComponent } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const modalRoot = document.getElementById("modal");
5 |
6 | const Modal: FunctionComponent = ({ children }) => {
7 | const elRef: MutableRefObject = useRef(null);
8 | if (!elRef.current) {
9 | elRef.current = document.createElement("div");
10 | }
11 |
12 | useEffect(() => {
13 | if (!modalRoot || !elRef.current) {
14 | return;
15 | }
16 | modalRoot.appendChild(elRef.current);
17 | return () => {
18 | if (elRef.current) {
19 | modalRoot.removeChild(elRef.current);
20 | }
21 | };
22 | }, []);
23 |
24 | return createPortal({children}
, elRef.current);
25 | };
26 |
27 | export default Modal;
28 |
--------------------------------------------------------------------------------
/typescript-5/src/Pet.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | interface Props {
5 | name: string;
6 | animal: string;
7 | breed: string;
8 | images: string[];
9 | location: string;
10 | id: number;
11 | }
12 |
13 | const Pet: FunctionComponent = (props) => {
14 | const { name, animal, breed, images, location, id } = props;
15 |
16 | let hero = "http://pets-images.dev-apis.com/pets/none.jpg";
17 | if (images.length) {
18 | hero = images[0];
19 | }
20 |
21 | return (
22 |
23 |
24 |

25 |
26 |
27 |
{name}
28 | {`${animal} — ${breed} — ${location}`}
29 |
30 |
31 | );
32 | };
33 |
34 | export default Pet;
35 |
--------------------------------------------------------------------------------
/typescript-5/src/Results.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from "react";
2 | import { Pet as PetType } from "./APIResponsesTypes";
3 | import Pet from "./Pet";
4 |
5 | const Results: FunctionComponent<{ pets: PetType[] }> = ({ pets }) => {
6 | return (
7 |
8 | {!pets.length ? (
9 |
No Pets Found
10 | ) : (
11 | pets.map((pet) => {
12 | return (
13 |
22 | );
23 | })
24 | )}
25 |
26 | );
27 | };
28 |
29 | export default Results;
30 |
--------------------------------------------------------------------------------
/typescript-5/src/ThemeContext.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext<[string, (theme: string) => void]>([
4 | "green",
5 | () => {},
6 | ]);
7 |
8 | export default ThemeContext;
9 |
--------------------------------------------------------------------------------
/typescript-5/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Adopt Me
10 |
11 |
12 |
13 |
14 | not rendered
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/typescript-5/src/useBreedList.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { Animal, BreedListAPIResponse } from "./APIResponsesTypes";
3 |
4 | const localCache: {
5 | [index: string]: string[];
6 | } = {};
7 |
8 | type Status = "unloaded" | "loading" | "loaded";
9 |
10 | export default function useBreedList(animal: Animal): [string[], Status] {
11 | const [breedList, setBreedList] = useState([] as string[]);
12 | const [status, setStatus] = useState("unloaded" as Status);
13 |
14 | useEffect(() => {
15 | if (!animal) {
16 | setBreedList([]);
17 | } else if (localCache[animal]) {
18 | setBreedList(localCache[animal]);
19 | } else {
20 | void requestBreedList();
21 | }
22 |
23 | async function requestBreedList() {
24 | setBreedList([]);
25 | setStatus("loading");
26 | const res = await fetch(
27 | `http://pets-v2.dev-apis.com/breeds?animal=${animal}`
28 | );
29 | const json = (await res.json()) as BreedListAPIResponse;
30 | localCache[animal] = json.breeds || [];
31 | setBreedList(localCache[animal]);
32 | setStatus("loaded");
33 | }
34 | }, [animal]);
35 |
36 | return [breedList, status];
37 | }
38 |
--------------------------------------------------------------------------------