├── .editorconfig
├── .gitignore
├── .prettierrc
├── 01-no-frills-react
└── src
│ ├── App.js
│ ├── index.html
│ └── style.css
├── 02-js-tools
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── index.html
│ └── style.css
├── 03-jsx
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── index.html
│ └── style.css
├── 04-hooks
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── SearchParams.js
│ ├── index.html
│ └── style.css
├── 05-useeffect
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── SearchParams.js
│ ├── index.html
│ └── style.css
├── 06-custom-hooks
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── SearchParams.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 07-component-composition
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── package.json
└── src
│ ├── App.js
│ ├── Pet.js
│ ├── Results.js
│ ├── SearchParams.js
│ ├── index.html
│ ├── style.css
│ └── useBreedList.js
├── 08-react-router
├── .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
│ ├── ErrorBoundary.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
├── 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
├── tailwindcss
├── .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.ts
│ ├── 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.ts
│ ├── 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.ts
│ ├── index.html
│ ├── style.css
│ └── useBreedList.ts
└── 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.ts
├── index.html
├── style.css
└── useBreedList.ts
└── 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 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 | package-lock.json
9 |
--------------------------------------------------------------------------------
/.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 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/02-js-tools/.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": 2022,
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 |
--------------------------------------------------------------------------------
/02-js-tools/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/02-js-tools/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/02-js-tools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "8.8.0",
15 | "eslint-config-prettier": "8.3.0",
16 | "parcel": "2.2.1",
17 | "prettier": "2.5.1"
18 | },
19 | "dependencies": {
20 | "react": "17.0.2",
21 | "react-dom": "17.0.2"
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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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": 2022,
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 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/03-jsx/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/03-jsx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "8.8.0",
15 | "eslint-config-prettier": "8.3.0",
16 | "eslint-plugin-import": "^2.25.4",
17 | "eslint-plugin-jsx-a11y": "^6.5.1",
18 | "eslint-plugin-react": "^7.28.0",
19 | "parcel": "2.2.1",
20 | "prettier": "2.5.1"
21 | },
22 | "dependencies": {
23 | "react": "17.0.2",
24 | "react-dom": "17.0.2"
25 | },
26 | "browserslist": [
27 | "last 2 Chrome versions"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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": 2022,
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 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/04-hooks/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/04-hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "8.8.0",
15 | "eslint-config-prettier": "8.3.0",
16 | "eslint-plugin-import": "^2.25.4",
17 | "eslint-plugin-jsx-a11y": "^6.5.1",
18 | "eslint-plugin-react": "^7.28.0",
19 | "eslint-plugin-react-hooks": "^4.3.0",
20 | "parcel": "2.2.1",
21 | "prettier": "2.5.1"
22 | },
23 | "dependencies": {
24 | "react": "17.0.2",
25 | "react-dom": "17.0.2"
26 | },
27 | "browserslist": [
28 | "last 2 Chrome versions"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/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/SearchParams.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | const ANIMALS = ["bird", "cat", "dog", "rabbit", "reptile"];
4 |
5 | const SearchParams = () => {
6 | const [location, updateLocation] = useState("");
7 | const [animal, updateAnimal] = useState("");
8 | const [breed, updateBreed] = useState("");
9 | const breeds = [];
10 | return (
11 |
12 |
63 |
64 | );
65 | };
66 |
67 | export default SearchParams;
68 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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": 2022,
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 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/05-useeffect/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/05-useeffect/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "8.8.0",
15 | "eslint-config-prettier": "8.3.0",
16 | "eslint-plugin-import": "^2.25.4",
17 | "eslint-plugin-jsx-a11y": "^6.5.1",
18 | "eslint-plugin-react": "^7.28.0",
19 | "eslint-plugin-react-hooks": "^4.3.0",
20 | "parcel": "2.2.1",
21 | "prettier": "2.5.1"
22 | },
23 | "dependencies": {
24 | "react": "17.0.2",
25 | "react-dom": "17.0.2"
26 | },
27 | "browserslist": [
28 | "last 2 Chrome versions"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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": 2022,
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 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/06-custom-hooks/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/06-custom-hooks/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "8.8.0",
15 | "eslint-config-prettier": "8.3.0",
16 | "eslint-plugin-import": "^2.25.4",
17 | "eslint-plugin-jsx-a11y": "^6.5.1",
18 | "eslint-plugin-react": "^7.28.0",
19 | "eslint-plugin-react-hooks": "^4.3.0",
20 | "parcel": "2.2.1",
21 | "prettier": "2.5.1"
22 | },
23 | "dependencies": {
24 | "react": "17.0.2",
25 | "react-dom": "17.0.2"
26 | },
27 | "browserslist": [
28 | "last 2 Chrome versions"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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/.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": 2022,
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 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/07-component-composition/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/07-component-composition/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "8.8.0",
15 | "eslint-config-prettier": "8.3.0",
16 | "eslint-plugin-import": "^2.25.4",
17 | "eslint-plugin-jsx-a11y": "^6.5.1",
18 | "eslint-plugin-react": "^7.28.0",
19 | "eslint-plugin-react-hooks": "^4.3.0",
20 | "parcel": "2.2.1",
21 | "prettier": "2.5.1"
22 | },
23 | "dependencies": {
24 | "react": "17.0.2",
25 | "react-dom": "17.0.2"
26 | },
27 | "browserslist": [
28 | "last 2 Chrome versions"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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/.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": 2022,
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 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/08-react-router/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/08-react-router/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "eslint": "8.8.0",
15 | "eslint-config-prettier": "8.3.0",
16 | "eslint-plugin-import": "^2.25.4",
17 | "eslint-plugin-jsx-a11y": "^6.5.1",
18 | "eslint-plugin-react": "^7.28.0",
19 | "eslint-plugin-react-hooks": "^4.3.0",
20 | "parcel": "2.2.1",
21 | "prettier": "2.5.1"
22 | },
23 | "dependencies": {
24 | "react": "17.0.2",
25 | "react-dom": "17.0.2",
26 | "react-router-dom": "^6.2.1"
27 | },
28 | "browserslist": [
29 | "last 2 Chrome versions"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/08-react-router/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
6 |
7 | const App = () => {
8 | return (
9 |
10 |
11 |
14 |
15 | } />
16 | } />
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | render(, document.getElementById("root"));
24 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "eslint": "8.8.0",
16 | "eslint-config-prettier": "8.3.0",
17 | "eslint-plugin-import": "2.25.4",
18 | "eslint-plugin-jsx-a11y": "^6.5.1",
19 | "eslint-plugin-react": "^7.28.0",
20 | "eslint-plugin-react-hooks": "4.3.0",
21 | "parcel": "2.2.1",
22 | "prettier": "2.5.1"
23 | },
24 | "dependencies": {
25 | "react": "17.0.2",
26 | "react-dom": "17.0.2",
27 | "react-router-dom": "^6.2.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
6 |
7 | const App = () => {
8 | return (
9 |
10 |
11 |
14 |
15 | } />
16 | } />
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | render(, document.getElementById("root"));
24 |
--------------------------------------------------------------------------------
/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 { useParams } 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.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 { animal, breed, city, state, description, name, images } =
22 | this.state;
23 |
24 | return (
25 |
26 |
27 |
28 |
{name}
29 |
{`${animal} — ${breed} — ${city}, ${state}`}
30 |
31 |
{description}
32 |
33 |
34 | );
35 | }
36 | }
37 |
38 | const WrappedDetails = () => {
39 | const params = useParams();
40 | return ;
41 | };
42 |
43 | export default WrappedDetails;
44 |
--------------------------------------------------------------------------------
/09-managing-state-in-class-components/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | class ErrorBoundary extends Component {
5 | state = { hasError: false };
6 | static getDerivedStateFromError() {
7 | return { hasError: true };
8 | }
9 | componentDidCatch(error, info) {
10 | console.error("ErrorBoundary caught an error", error, info);
11 | }
12 | render() {
13 | if (this.state.hasError) {
14 | return (
15 |
16 | There was an error with this listing. Click here{" "}
17 | to back to the home page or wait five seconds.
18 |
19 | );
20 | }
21 |
22 | return this.props.children;
23 | }
24 | }
25 |
26 | export default ErrorBoundary;
27 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/10-error-boundaries/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/10-error-boundaries/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/10-error-boundaries/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "eslint": "8.8.0",
16 | "eslint-config-prettier": "8.3.0",
17 | "eslint-plugin-import": "2.25.4",
18 | "eslint-plugin-jsx-a11y": "^6.5.1",
19 | "eslint-plugin-react": "^7.28.0",
20 | "eslint-plugin-react-hooks": "4.3.0",
21 | "parcel": "2.2.1",
22 | "prettier": "2.5.1"
23 | },
24 | "dependencies": {
25 | "react": "17.0.2",
26 | "react-dom": "17.0.2",
27 | "react-router-dom": "^6.2.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
6 |
7 | const App = () => {
8 | return (
9 |
10 |
11 |
14 |
15 | } />
16 | } />
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | render(, document.getElementById("root"));
24 |
--------------------------------------------------------------------------------
/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 { useParams } 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.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 { animal, breed, city, state, description, name, images } =
23 | this.state;
24 |
25 | return (
26 |
27 |
28 |
29 |
{name}
30 |
{`${animal} — ${breed} — ${city}, ${state}`}
31 |
32 |
{description}
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | const WrappedDetails = () => {
40 | const params = useParams();
41 | return (
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default WrappedDetails;
49 |
--------------------------------------------------------------------------------
/10-error-boundaries/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/11-context/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/11-context/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/11-context/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "eslint": "8.8.0",
16 | "eslint-config-prettier": "8.3.0",
17 | "eslint-plugin-import": "2.25.4",
18 | "eslint-plugin-jsx-a11y": "^6.5.1",
19 | "eslint-plugin-react": "^7.28.0",
20 | "eslint-plugin-react-hooks": "4.3.0",
21 | "parcel": "2.2.1",
22 | "prettier": "2.5.1"
23 | },
24 | "dependencies": {
25 | "react": "17.0.2",
26 | "react-dom": "17.0.2",
27 | "react-router-dom": "^6.2.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/11-context/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/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 { useParams } from "react-router-dom";
3 | import Carousel from "./Carousel";
4 | import ErrorBoundary from "./ErrorBoundary";
5 | import ThemeContext from "./ThemeContext";
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.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 { animal, breed, city, state, description, name, images } =
24 | this.state;
25 |
26 | return (
27 |
28 |
29 |
30 |
{name}
31 |
{`${animal} — ${breed} — ${city}, ${state}`}
32 |
33 | {([theme]) => (
34 |
35 | )}
36 |
37 |
{description}
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | const WrappedDetails = () => {
45 | const params = useParams();
46 | return (
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | export default WrappedDetails;
54 |
--------------------------------------------------------------------------------
/11-context/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/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 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/12-portals-and-refs/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/12-portals-and-refs/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/12-portals-and-refs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "eslint": "8.8.0",
16 | "eslint-config-prettier": "8.3.0",
17 | "eslint-plugin-import": "2.25.4",
18 | "eslint-plugin-jsx-a11y": "^6.5.1",
19 | "eslint-plugin-react": "^7.28.0",
20 | "eslint-plugin-react-hooks": "4.3.0",
21 | "parcel": "2.2.1",
22 | "prettier": "2.5.1"
23 | },
24 | "dependencies": {
25 | "react": "17.0.2",
26 | "react-dom": "17.0.2",
27 | "react-router-dom": "^6.2.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/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, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/12-portals-and-refs/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal = ({ children }) => {
5 | const elRef = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | modalRoot.appendChild(elRef.current);
13 | return () => modalRoot.removeChild(elRef.current);
14 | }, []);
15 |
16 | return createPortal({children}
, elRef.current);
17 | };
18 |
19 | export default Modal;
20 |
--------------------------------------------------------------------------------
/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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/code-splitting/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/code-splitting/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/code-splitting/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/code-splitting/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "eslint": "8.8.0",
16 | "eslint-config-prettier": "8.3.0",
17 | "eslint-plugin-import": "2.25.4",
18 | "eslint-plugin-jsx-a11y": "^6.5.1",
19 | "eslint-plugin-react": "^7.28.0",
20 | "eslint-plugin-react-hooks": "4.3.0",
21 | "parcel": "2.2.1",
22 | "prettier": "2.5.1"
23 | },
24 | "dependencies": {
25 | "react": "17.0.2",
26 | "react-dom": "17.0.2",
27 | "react-router-dom": "^6.2.1"
28 | },
29 | "browserslist": [
30 | "last 2 Chrome versions"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/code-splitting/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import { useState, StrictMode, lazy, Suspense } from "react";
3 | import { BrowserRouter, Routes, Route, 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 | render(, document.getElementById("root"));
31 |
--------------------------------------------------------------------------------
/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, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/code-splitting/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal = ({ children }) => {
5 | const elRef = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | modalRoot.appendChild(elRef.current);
13 | return () => modalRoot.removeChild(elRef.current);
14 | }, []);
15 |
16 | return createPortal({children}
, elRef.current);
17 | };
18 |
19 | export default Modal;
20 |
--------------------------------------------------------------------------------
/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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/redux/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/redux/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "eslint": "8.8.0",
16 | "eslint-config-prettier": "8.3.0",
17 | "eslint-plugin-import": "2.25.4",
18 | "eslint-plugin-jsx-a11y": "^6.5.1",
19 | "eslint-plugin-react": "^7.28.0",
20 | "eslint-plugin-react-hooks": "4.3.0",
21 | "parcel": "2.2.1",
22 | "prettier": "2.5.1"
23 | },
24 | "dependencies": {
25 | "react": "17.0.2",
26 | "react-dom": "17.0.2",
27 | "react-redux": "7.2.6",
28 | "react-router-dom": "6.2.1",
29 | "redux": "4.1.2"
30 | },
31 | "browserslist": [
32 | "last 2 Chrome versions"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/redux/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
6 | import { Provider } from "react-redux";
7 | import store from "./store";
8 |
9 | const App = () => {
10 | return (
11 |
12 |
13 |
14 |
17 |
18 | } />
19 | } />
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/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/Details.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { useParams } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import Carousel from "./Carousel";
5 | import ErrorBoundary from "./ErrorBoundary";
6 | import Modal from "./Modal";
7 |
8 | class Details extends Component {
9 | state = { loading: true, showModal: false };
10 |
11 | async componentDidMount() {
12 | const res = await fetch(
13 | `http://pets-v2.dev-apis.com/pets?id=${this.props.params.id}`
14 | );
15 | const json = await res.json();
16 | this.setState(Object.assign({ loading: false }, json.pets[0]));
17 | }
18 | toggleModal = () => this.setState({ showModal: !this.state.showModal });
19 | adopt = () => (window.location = "http://bit.ly/pet-adopt");
20 | render() {
21 | if (this.state.loading) {
22 | return loading …
;
23 | }
24 |
25 | const { animal, breed, city, state, description, name, images, showModal } =
26 | this.state;
27 |
28 | return (
29 |
30 |
31 |
32 |
{name}
33 |
{`${animal} — ${breed} — ${city}, ${state}`}
34 |
40 |
{description}
41 | {showModal ? (
42 |
43 |
44 |
Would you like to adopt {name}?
45 |
49 |
50 |
51 | ) : null}
52 |
53 |
54 | );
55 | }
56 | }
57 |
58 | const mapStateToProps = ({ theme }) => ({ theme });
59 | const ReduxWrappedDetails = connect(mapStateToProps)(Details);
60 |
61 | const WrappedDetails = () => {
62 | const params = useParams();
63 | return (
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default WrappedDetails;
71 |
--------------------------------------------------------------------------------
/redux/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/redux/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal = ({ children }) => {
5 | const elRef = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | modalRoot.appendChild(elRef.current);
13 | return () => modalRoot.removeChild(elRef.current);
14 | }, []);
15 |
16 | return createPortal({children}
, elRef.current);
17 | };
18 |
19 | export default Modal;
20 |
--------------------------------------------------------------------------------
/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(location) {
2 | return { type: "CHANGE_ANIMAL", payload: location };
3 | }
4 |
--------------------------------------------------------------------------------
/redux/src/actionCreators/changeBreed.js:
--------------------------------------------------------------------------------
1 | export default function changeBreed(location) {
2 | return { type: "CHANGE_BREED", payload: location };
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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 | case "CHANGE_ANIMAL":
6 | return action.payload;
7 | default:
8 | return state;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/server-side-rendering-1/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/server-side-rendering-1/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/server-side-rendering-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "build": "parcel build",
8 | "dev": "parcel src/index.html",
9 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
10 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
11 | "start": "npm -s run build && node dist/backend/index.js"
12 | },
13 | "author": "Brian Holt ",
14 | "license": "Apache-2.0",
15 | "devDependencies": {
16 | "@babel/plugin-proposal-class-properties": "7.16.7",
17 | "eslint": "8.8.0",
18 | "eslint-config-prettier": "8.3.0",
19 | "eslint-plugin-import": "2.25.4",
20 | "eslint-plugin-jsx-a11y": "^6.5.1",
21 | "eslint-plugin-react": "^7.28.0",
22 | "eslint-plugin-react-hooks": "4.3.0",
23 | "parcel": "2.2.1",
24 | "prettier": "2.5.1"
25 | },
26 | "dependencies": {
27 | "express": "4.17.3",
28 | "react": "17.0.2",
29 | "react-dom": "17.0.2",
30 | "react-router-dom": "^6.2.1"
31 | },
32 | "browserslist": [
33 | "last 2 Chrome versions"
34 | ],
35 | "targets": {
36 | "frontend": {
37 | "source": [
38 | "src/index.html"
39 | ],
40 | "publicUrl": "/frontend"
41 | },
42 | "backend": {
43 | "source": "server/index.js",
44 | "optimize": false,
45 | "context": "node",
46 | "engines": {
47 | "node": ">=16"
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/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/server";
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/frontend/index.html").toString();
10 |
11 | const parts = html.split("not rendered");
12 |
13 | const app = express();
14 |
15 | app.use("/frontend", express.static("dist/frontend"));
16 | app.use((req, res) => {
17 | const reactMarkup = (
18 |
19 |
20 |
21 | );
22 |
23 | res.send(`${parts[0]}${renderToString(reactMarkup)}${parts[1]}`);
24 | res.end();
25 | });
26 |
27 | console.log(`listening on http://localhost:${PORT}`);
28 | app.listen(PORT);
29 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/App.js:
--------------------------------------------------------------------------------
1 | import SearchParams from "./SearchParams";
2 | import { StrictMode, useState } from "react";
3 | import { Routes, Route, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import ThemeContext from "./ThemeContext";
6 |
7 | const App = () => {
8 | const theme = useState("darkblue");
9 | return (
10 |
11 |
12 |
15 |
16 | } />
17 | } />
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/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 } 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, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/server-side-rendering-1/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal = ({ children }) => {
5 | const elRef = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | modalRoot.appendChild(elRef.current);
13 | return () => modalRoot.removeChild(elRef.current);
14 | }, []);
15 |
16 | return createPortal({children}
, elRef.current);
17 | };
18 |
19 | export default Modal;
20 |
--------------------------------------------------------------------------------
/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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/server-side-rendering-2/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/server-side-rendering-2/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/server-side-rendering-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "build": "parcel build",
8 | "dev": "parcel src/index.html",
9 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
10 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
11 | "start": "npm -s run build && node dist/backend/index.js"
12 | },
13 | "author": "Brian Holt ",
14 | "license": "Apache-2.0",
15 | "devDependencies": {
16 | "@babel/plugin-proposal-class-properties": "7.16.7",
17 | "eslint": "8.8.0",
18 | "eslint-config-prettier": "8.3.0",
19 | "eslint-plugin-import": "2.25.4",
20 | "eslint-plugin-jsx-a11y": "^6.5.1",
21 | "eslint-plugin-react": "^7.28.0",
22 | "eslint-plugin-react-hooks": "4.3.0",
23 | "parcel": "2.2.1",
24 | "prettier": "2.5.1"
25 | },
26 | "dependencies": {
27 | "express": "4.17.3",
28 | "react": "17.0.2",
29 | "react-dom": "17.0.2",
30 | "react-router-dom": "^6.2.1"
31 | },
32 | "browserslist": [
33 | "last 2 Chrome versions"
34 | ],
35 | "targets": {
36 | "frontend": {
37 | "source": [
38 | "src/index.html"
39 | ],
40 | "publicUrl": "/frontend"
41 | },
42 | "backend": {
43 | "source": "server/index.js",
44 | "optimize": false,
45 | "context": "node",
46 | "engines": {
47 | "node": ">=16"
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/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/server";
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/frontend/index.html").toString();
10 |
11 | const parts = html.split("not rendered");
12 |
13 | const app = express();
14 |
15 | app.use("/frontend", express.static("dist/frontend"));
16 | app.use((req, res) => {
17 | res.write(parts[0]);
18 | const reactMarkup = (
19 |
20 |
21 |
22 | );
23 |
24 | const stream = renderToNodeStream(reactMarkup);
25 | stream.pipe(res, { end: false });
26 | stream.on("end", () => {
27 | res.write(parts[1]);
28 | res.end();
29 | });
30 | });
31 |
32 | console.log(`listening on http://localhost:${PORT}`);
33 | app.listen(PORT);
34 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/App.js:
--------------------------------------------------------------------------------
1 | import SearchParams from "./SearchParams";
2 | import { StrictMode, useState } from "react";
3 | import { Routes, Route, Link } from "react-router-dom";
4 | import Details from "./Details";
5 | import ThemeContext from "./ThemeContext";
6 |
7 | const App = () => {
8 | const theme = useState("darkblue");
9 | return (
10 |
11 |
12 |
15 |
16 | } />
17 | } />
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/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 } 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, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/server-side-rendering-2/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal = ({ children }) => {
5 | const elRef = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | modalRoot.appendChild(elRef.current);
13 | return () => modalRoot.removeChild(elRef.current);
14 | }, []);
15 |
16 | return createPortal({children}
, elRef.current);
17 | };
18 |
19 | export default Modal;
20 |
--------------------------------------------------------------------------------
/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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/tailwindcss/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/tailwindcss/.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": 2022,
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 |
--------------------------------------------------------------------------------
/tailwindcss/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/tailwindcss/.postcssrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": {
3 | "autoprefixer": {},
4 | "tailwindcss": {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tailwindcss/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/tailwindcss/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "@tailwindcss/forms": "^0.4.0",
16 | "autoprefixer": "^10.4.2",
17 | "eslint": "8.8.0",
18 | "eslint-config-prettier": "8.3.0",
19 | "eslint-plugin-import": "2.25.4",
20 | "eslint-plugin-jsx-a11y": "^6.5.1",
21 | "eslint-plugin-react": "^7.28.0",
22 | "eslint-plugin-react-hooks": "4.3.0",
23 | "parcel": "2.2.1",
24 | "postcss": "^8.4.6",
25 | "prettier": "2.5.1",
26 | "tailwindcss": "^3.0.22"
27 | },
28 | "dependencies": {
29 | "react": "17.0.2",
30 | "react-dom": "17.0.2",
31 | "react-router-dom": "^6.2.1"
32 | },
33 | "browserslist": [
34 | "last 2 Chrome versions"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/tailwindcss/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
6 | import ThemeContext from "./ThemeContext";
7 |
8 | const App = () => {
9 | const theme = useState("darkblue");
10 | return (
11 |
12 |
13 |
20 |
21 |
22 |
23 | Adopt Me!
24 |
25 |
26 |
27 | } />
28 | } />
29 |
30 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | render(, document.getElementById("root"));
38 |
--------------------------------------------------------------------------------
/tailwindcss/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 |
--------------------------------------------------------------------------------
/tailwindcss/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/tailwindcss/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal = ({ children }) => {
5 | const elRef = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | modalRoot.appendChild(elRef.current);
13 | return () => modalRoot.removeChild(elRef.current);
14 | }, []);
15 |
16 | return createPortal({children}
, elRef.current);
17 | };
18 |
19 | export default Modal;
20 |
--------------------------------------------------------------------------------
/tailwindcss/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 |
--------------------------------------------------------------------------------
/tailwindcss/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 |
--------------------------------------------------------------------------------
/tailwindcss/src/ThemeContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const ThemeContext = createContext(["green", () => {}]);
4 |
5 | export default ThemeContext;
6 |
--------------------------------------------------------------------------------
/tailwindcss/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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/tailwindcss/src/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/tailwindcss/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 |
--------------------------------------------------------------------------------
/tailwindcss/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: "jit",
3 | content: ["./src/*.{html,js}"],
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {},
8 | plugins: [require("@tailwindcss/forms")],
9 | };
10 |
--------------------------------------------------------------------------------
/testing/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@babel/plugin-proposal-class-properties"],
3 | "presets": [
4 | "@babel/preset-env",
5 | [
6 | "@babel/preset-react",
7 | {
8 | "runtime": "automatic"
9 | }
10 | ]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/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 | "parserOptions": {
16 | "ecmaVersion": 2022,
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 |
--------------------------------------------------------------------------------
/testing/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/testing/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/testing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
10 | "test": "jest",
11 | "test:coverage": "jest --coverage",
12 | "test:watch": "jest --watch"
13 | },
14 | "author": "Brian Holt ",
15 | "license": "Apache-2.0",
16 | "devDependencies": {
17 | "@babel/plugin-proposal-class-properties": "7.16.7",
18 | "@babel/preset-env": "7.16.11",
19 | "@babel/preset-react": "7.16.7",
20 | "@testing-library/react": "12.1.3",
21 | "@testing-library/react-hooks": "7.0.2",
22 | "eslint": "8.8.0",
23 | "eslint-config-prettier": "8.3.0",
24 | "eslint-plugin-import": "2.25.4",
25 | "eslint-plugin-jsx-a11y": "6.5.1",
26 | "eslint-plugin-react": "7.28.0",
27 | "eslint-plugin-react-hooks": "4.3.0",
28 | "jest": "27.5.1",
29 | "jest-fetch-mock": "3.0.3",
30 | "parcel": "2.2.1",
31 | "prettier": "2.5.1",
32 | "react-test-renderer": "17.0.2"
33 | },
34 | "dependencies": {
35 | "react": "17.0.2",
36 | "react-dom": "17.0.2",
37 | "react-router-dom": "6.2.1"
38 | },
39 | "browserslist": [
40 | "last 2 Chrome versions"
41 | ],
42 | "jest": {
43 | "automock": false,
44 | "setupFiles": [
45 | "./src/setupJest.js"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/testing/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/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/Details.js:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { useParams } from "react-router-dom";
3 | import Carousel from "./Carousel";
4 | import ErrorBoundary from "./ErrorBoundary";
5 | import ThemeContext from "./ThemeContext";
6 | import Modal from "./Modal";
7 |
8 | class Details extends Component {
9 | state = { loading: true, showModal: false };
10 |
11 | async componentDidMount() {
12 | const res = await fetch(
13 | `http://pets-v2.dev-apis.com/pets?id=${this.props.params.id}`
14 | );
15 | const json = await res.json();
16 | this.setState(Object.assign({ loading: false }, json.pets[0]));
17 | }
18 | toggleModal = () => this.setState({ showModal: !this.state.showModal });
19 | adopt = () => (window.location = "http://bit.ly/pet-adopt");
20 | render() {
21 | if (this.state.loading) {
22 | return loading …
;
23 | }
24 |
25 | const { animal, breed, city, state, description, name, images, showModal } =
26 | this.state;
27 |
28 | return (
29 |
30 |
31 |
32 |
{name}
33 |
{`${animal} — ${breed} — ${city}, ${state}`}
34 |
35 | {([theme]) => (
36 |
42 | )}
43 |
44 |
{description}
45 | {showModal ? (
46 |
47 |
48 |
Would you like to adopt {name}?
49 |
53 |
54 |
55 | ) : null}
56 |
57 |
58 | );
59 | }
60 | }
61 |
62 | const WrappedDetails = () => {
63 | const params = useParams();
64 | return (
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | export default WrappedDetails;
72 |
--------------------------------------------------------------------------------
/testing/src/ErrorBoundary.js:
--------------------------------------------------------------------------------
1 | // mostly code from reactjs.org/docs/error-boundaries.html
2 | import { Component } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/testing/src/Modal.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal = ({ children }) => {
5 | const elRef = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | modalRoot.appendChild(elRef.current);
13 | return () => modalRoot.removeChild(elRef.current);
14 | }, []);
15 |
16 | return createPortal({children}
, elRef.current);
17 | };
18 |
19 | export default Modal;
20 |
--------------------------------------------------------------------------------
/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 | /**
2 | * @jest-environment jsdom
3 | */
4 |
5 | import { expect, test } from "@jest/globals";
6 | import { render } from "@testing-library/react";
7 | import Carousel from "../Carousel.js";
8 |
9 | test("lets users click on thumbnails to make them the hero", async () => {
10 | const images = ["0.jpg", "1.jpg", "2.jpg", "3.jpg"];
11 | const carousel = render();
12 |
13 | const hero = await carousel.findByTestId("hero");
14 | expect(hero.src).toContain(images[0]);
15 |
16 | for (let i = 0; i < images.length; i++) {
17 | const image = images[i];
18 |
19 | const thumb = await carousel.findByTestId(`thumbnail${i}`);
20 | thumb.click();
21 |
22 | expect(hero.src).toContain(image);
23 | expect(thumb.classList).toContain("active");
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/testing/src/__tests__/Pet.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 |
5 | import { expect, test } from "@jest/globals";
6 | import { render } from "@testing-library/react";
7 | import { StaticRouter } from "react-router-dom/server";
8 | import Pet from "../Pet.js";
9 |
10 | test("displays a default thumbnail", async () => {
11 | const pet = render(
12 |
13 |
14 |
15 | );
16 |
17 | const petThumbnail = await pet.findByTestId("thumbnail");
18 | expect(petThumbnail.src).toContain("none.jpg");
19 | });
20 |
21 | test("displays a non-default thumbnail", async () => {
22 | const pet = render(
23 |
24 |
25 |
26 | );
27 |
28 | const petThumbnail = await pet.findByTestId("thumbnail");
29 | expect(petThumbnail.src).toContain("1.jpg");
30 | });
31 |
--------------------------------------------------------------------------------
/testing/src/__tests__/useBreedList.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 |
5 | import { expect, test } from "@jest/globals";
6 | import { renderHook } from "@testing-library/react-hooks";
7 | import useBreedList from "../useBreedList.js";
8 |
9 | test("gives an empty list with no animal", async () => {
10 | const { result } = renderHook(() => useBreedList(""));
11 |
12 | const [breedList, status] = result.current;
13 |
14 | expect(breedList).toHaveLength(0);
15 | expect(status).toBe("unloaded");
16 | });
17 |
18 | test("gives back breeds with an animal", async () => {
19 | const breeds = [
20 | "Havanese",
21 | "Bichon Frise",
22 | "Poodle",
23 | "Maltese",
24 | "Golden Retriever",
25 | "Labrador",
26 | "Husky",
27 | ];
28 | fetch.mockResponseOnce(
29 | JSON.stringify({
30 | animal: "dog",
31 | breeds,
32 | })
33 | );
34 | const { result, waitForNextUpdate } = renderHook(() => useBreedList("dog"));
35 |
36 | await waitForNextUpdate();
37 |
38 | const [breedList, status] = result.current;
39 | expect(status).toBe("loaded");
40 | expect(breedList).toEqual(breeds);
41 | });
42 |
--------------------------------------------------------------------------------
/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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "prettier"
10 | ],
11 | "rules": {
12 | "react/prop-types": 0,
13 | "react/react-in-jsx-scope": 0,
14 | "@typescript-eslint/no-empty-function": 0
15 | },
16 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
17 | "parser": "@typescript-eslint/parser",
18 | "parserOptions": {
19 | "ecmaVersion": 2022,
20 | "sourceType": "module",
21 | "ecmaFeatures": {
22 | "jsx": true
23 | }
24 | },
25 | "env": {
26 | "es6": true,
27 | "browser": true,
28 | "node": true
29 | },
30 | "settings": {
31 | "import/parsers": {
32 | "@typescript-eslint/parser": [".ts", ".tsx"]
33 | },
34 | "import/resolver": {
35 | "typescript": {
36 | "alwaysTryTypes": true
37 | }
38 | },
39 | "react": {
40 | "version": "detect"
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/typescript-1/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/typescript-1/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/typescript-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "@types/react": "17.0.39",
16 | "@types/react-dom": "17.0.11",
17 | "@typescript-eslint/eslint-plugin": "^5.12.0",
18 | "@typescript-eslint/parser": "^5.12.0",
19 | "eslint": "8.8.0",
20 | "eslint-config-prettier": "8.3.0",
21 | "eslint-import-resolver-typescript": "^2.5.0",
22 | "eslint-plugin-import": "2.25.4",
23 | "eslint-plugin-jsx-a11y": "^6.5.1",
24 | "eslint-plugin-react": "^7.28.0",
25 | "eslint-plugin-react-hooks": "4.3.0",
26 | "parcel": "2.2.1",
27 | "prettier": "2.5.1",
28 | "typescript": "4.5.5"
29 | },
30 | "dependencies": {
31 | "react": "17.0.2",
32 | "react-dom": "17.0.2",
33 | "react-router-dom": "^6.2.1"
34 | },
35 | "browserslist": [
36 | "last 2 Chrome versions"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/typescript-1/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/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, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/typescript-1/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal: FunctionComponent = ({ children }) => {
5 | const elRef: MutableRefObject = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | if (!modalRoot || !elRef.current) {
13 | return;
14 | }
15 | modalRoot.appendChild(elRef.current);
16 | return () => {
17 | if (elRef.current) {
18 | modalRoot.removeChild(elRef.current);
19 | }
20 | }
21 | }, []);
22 |
23 | return createPortal({children}
, elRef.current);
24 | };
25 |
26 | export default Modal;
27 |
--------------------------------------------------------------------------------
/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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "prettier"
10 | ],
11 | "rules": {
12 | "react/prop-types": 0,
13 | "react/react-in-jsx-scope": 0,
14 | "@typescript-eslint/no-empty-function": 0
15 | },
16 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
17 | "parser": "@typescript-eslint/parser",
18 | "parserOptions": {
19 | "ecmaVersion": 2022,
20 | "sourceType": "module",
21 | "ecmaFeatures": {
22 | "jsx": true
23 | }
24 | },
25 | "env": {
26 | "es6": true,
27 | "browser": true,
28 | "node": true
29 | },
30 | "settings": {
31 | "import/parsers": {
32 | "@typescript-eslint/parser": [".ts", ".tsx"]
33 | },
34 | "import/resolver": {
35 | "typescript": {
36 | "alwaysTryTypes": true
37 | }
38 | },
39 | "react": {
40 | "version": "detect"
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/typescript-2/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/typescript-2/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/typescript-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "@types/react": "17.0.39",
16 | "@types/react-dom": "17.0.11",
17 | "@typescript-eslint/eslint-plugin": "^5.12.0",
18 | "@typescript-eslint/parser": "^5.12.0",
19 | "eslint": "8.8.0",
20 | "eslint-config-prettier": "8.3.0",
21 | "eslint-import-resolver-typescript": "^2.5.0",
22 | "eslint-plugin-import": "2.25.4",
23 | "eslint-plugin-jsx-a11y": "^6.5.1",
24 | "eslint-plugin-react": "^7.28.0",
25 | "eslint-plugin-react-hooks": "4.3.0",
26 | "parcel": "2.2.1",
27 | "prettier": "2.5.1",
28 | "typescript": "4.5.5"
29 | },
30 | "dependencies": {
31 | "react": "17.0.2",
32 | "react-dom": "17.0.2",
33 | "react-router-dom": "^6.2.1"
34 | },
35 | "browserslist": [
36 | "last 2 Chrome versions"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/typescript-2/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 |
--------------------------------------------------------------------------------
/typescript-2/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/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, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/typescript-2/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal: FunctionComponent = ({ children }) => {
5 | const elRef: MutableRefObject = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | if (!modalRoot || !elRef.current) {
13 | return;
14 | }
15 | modalRoot.appendChild(elRef.current);
16 | return () => {
17 | if (elRef.current) {
18 | modalRoot.removeChild(elRef.current);
19 | }
20 | }
21 | }, []);
22 |
23 | return createPortal({children}
, elRef.current);
24 | };
25 |
26 | export default Modal;
27 |
--------------------------------------------------------------------------------
/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.ts:
--------------------------------------------------------------------------------
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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "prettier"
10 | ],
11 | "rules": {
12 | "react/prop-types": 0,
13 | "react/react-in-jsx-scope": 0,
14 | "@typescript-eslint/no-empty-function": 0
15 | },
16 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
17 | "parser": "@typescript-eslint/parser",
18 | "parserOptions": {
19 | "ecmaVersion": 2022,
20 | "sourceType": "module",
21 | "ecmaFeatures": {
22 | "jsx": true
23 | }
24 | },
25 | "env": {
26 | "es6": true,
27 | "browser": true,
28 | "node": true
29 | },
30 | "settings": {
31 | "import/parsers": {
32 | "@typescript-eslint/parser": [".ts", ".tsx"]
33 | },
34 | "import/resolver": {
35 | "typescript": {
36 | "alwaysTryTypes": true
37 | }
38 | },
39 | "react": {
40 | "version": "detect"
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/typescript-3/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/typescript-3/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/typescript-3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "@types/react": "17.0.39",
16 | "@types/react-dom": "17.0.11",
17 | "@typescript-eslint/eslint-plugin": "^5.12.0",
18 | "@typescript-eslint/parser": "^5.12.0",
19 | "eslint": "8.8.0",
20 | "eslint-config-prettier": "8.3.0",
21 | "eslint-import-resolver-typescript": "^2.5.0",
22 | "eslint-plugin-import": "2.25.4",
23 | "eslint-plugin-jsx-a11y": "^6.5.1",
24 | "eslint-plugin-react": "^7.28.0",
25 | "eslint-plugin-react-hooks": "4.3.0",
26 | "parcel": "2.2.1",
27 | "prettier": "2.5.1",
28 | "typescript": "4.5.5"
29 | },
30 | "dependencies": {
31 | "react": "17.0.2",
32 | "react-dom": "17.0.2",
33 | "react-router-dom": "^6.2.1"
34 | },
35 | "browserslist": [
36 | "last 2 Chrome versions"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/typescript-3/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 |
--------------------------------------------------------------------------------
/typescript-3/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/typescript-3/src/Carousel.tsx:
--------------------------------------------------------------------------------
1 | import { Component, MouseEvent } 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() {
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 } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
9 | }
10 | componentDidCatch(error: Error, info: ErrorInfo): void {
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/typescript-3/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal: FunctionComponent = ({ children }) => {
5 | const elRef: MutableRefObject = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | if (!modalRoot || !elRef.current) {
13 | return;
14 | }
15 | modalRoot.appendChild(elRef.current);
16 | return () => {
17 | if (elRef.current) {
18 | modalRoot.removeChild(elRef.current);
19 | }
20 | }
21 | }, []);
22 |
23 | return createPortal({children}
, elRef.current);
24 | };
25 |
26 | export default Modal;
27 |
--------------------------------------------------------------------------------
/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.ts:
--------------------------------------------------------------------------------
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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "prettier"
10 | ],
11 | "rules": {
12 | "react/prop-types": 0,
13 | "react/react-in-jsx-scope": 0,
14 | "@typescript-eslint/no-empty-function": 0
15 | },
16 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
17 | "parser": "@typescript-eslint/parser",
18 | "parserOptions": {
19 | "ecmaVersion": 2022,
20 | "sourceType": "module",
21 | "ecmaFeatures": {
22 | "jsx": true
23 | }
24 | },
25 | "env": {
26 | "es6": true,
27 | "browser": true,
28 | "node": true
29 | },
30 | "settings": {
31 | "import/parsers": {
32 | "@typescript-eslint/parser": [".ts", ".tsx"]
33 | },
34 | "import/resolver": {
35 | "typescript": {
36 | "alwaysTryTypes": true
37 | }
38 | },
39 | "react": {
40 | "version": "detect"
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/typescript-4/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/typescript-4/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/typescript-4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet"
10 | },
11 | "author": "Brian Holt ",
12 | "license": "Apache-2.0",
13 | "devDependencies": {
14 | "@babel/plugin-proposal-class-properties": "7.16.7",
15 | "@types/react": "17.0.39",
16 | "@types/react-dom": "17.0.11",
17 | "@typescript-eslint/eslint-plugin": "^5.12.0",
18 | "@typescript-eslint/parser": "^5.12.0",
19 | "eslint": "8.8.0",
20 | "eslint-config-prettier": "8.3.0",
21 | "eslint-import-resolver-typescript": "^2.5.0",
22 | "eslint-plugin-import": "2.25.4",
23 | "eslint-plugin-jsx-a11y": "^6.5.1",
24 | "eslint-plugin-react": "^7.28.0",
25 | "eslint-plugin-react-hooks": "4.3.0",
26 | "parcel": "2.2.1",
27 | "prettier": "2.5.1",
28 | "typescript": "4.5.5"
29 | },
30 | "dependencies": {
31 | "react": "17.0.2",
32 | "react-dom": "17.0.2",
33 | "react-router-dom": "^6.2.1"
34 | },
35 | "browserslist": [
36 | "last 2 Chrome versions"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/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 | 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-4/src/App.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/typescript-4/src/Carousel.tsx:
--------------------------------------------------------------------------------
1 | import { Component, MouseEvent } 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() {
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 } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
9 | }
10 | componentDidCatch(error: Error, info: ErrorInfo): void {
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/typescript-4/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal: FunctionComponent = ({ children }) => {
5 | const elRef: MutableRefObject = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | if (!modalRoot || !elRef.current) {
13 | return;
14 | }
15 | modalRoot.appendChild(elRef.current);
16 | return () => {
17 | if (elRef.current) {
18 | modalRoot.removeChild(elRef.current);
19 | }
20 | }
21 | }, []);
22 |
23 | return createPortal({children}
, elRef.current);
24 | };
25 |
26 | export default Modal;
27 |
--------------------------------------------------------------------------------
/typescript-4/src/Pet.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | interface IProps {
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.ts:
--------------------------------------------------------------------------------
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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/typescript-4/src/useBreedList.ts:
--------------------------------------------------------------------------------
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 | 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 | "plugins": ["@babel/plugin-proposal-class-properties"]
3 | }
4 |
--------------------------------------------------------------------------------
/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 | "prettier"
10 | ],
11 | "rules": {
12 | "react/prop-types": 0,
13 | "react/react-in-jsx-scope": 0,
14 | "@typescript-eslint/no-empty-function": 0
15 | },
16 | "plugins": ["react", "import", "jsx-a11y", "@typescript-eslint"],
17 | "parser": "@typescript-eslint/parser",
18 | "parserOptions": {
19 | "ecmaVersion": 2022,
20 | "sourceType": "module",
21 | "ecmaFeatures": {
22 | "jsx": true
23 | }
24 | },
25 | "env": {
26 | "es6": true,
27 | "browser": true,
28 | "node": true
29 | },
30 | "settings": {
31 | "import/parsers": {
32 | "@typescript-eslint/parser": [".ts", ".tsx"]
33 | },
34 | "import/resolver": {
35 | "typescript": {
36 | "alwaysTryTypes": true
37 | }
38 | },
39 | "react": {
40 | "version": "detect"
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/typescript-5/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache/
3 | dist/
4 | .env
5 | .DS_Store
6 | coverage/
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/typescript-5/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/typescript-5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adopt-me",
3 | "version": "7.0.0",
4 | "description": "Adopt pets via adopt-me",
5 | "main": "src/App.js",
6 | "scripts": {
7 | "dev": "parcel src/index.html",
8 | "format": "prettier --write \"src/**/*.{js,jsx}\"",
9 | "lint": "eslint \"src/**/*.{js,jsx}\" --quiet",
10 | "typecheck": "tsc --noEmit"
11 | },
12 | "author": "Brian Holt ",
13 | "license": "Apache-2.0",
14 | "devDependencies": {
15 | "@babel/plugin-proposal-class-properties": "7.16.7",
16 | "@types/react": "17.0.39",
17 | "@types/react-dom": "17.0.11",
18 | "@typescript-eslint/eslint-plugin": "^5.12.0",
19 | "@typescript-eslint/parser": "^5.12.0",
20 | "eslint": "8.8.0",
21 | "eslint-config-prettier": "8.3.0",
22 | "eslint-import-resolver-typescript": "^2.5.0",
23 | "eslint-plugin-import": "2.25.4",
24 | "eslint-plugin-jsx-a11y": "^6.5.1",
25 | "eslint-plugin-react": "^7.28.0",
26 | "eslint-plugin-react-hooks": "4.3.0",
27 | "parcel": "2.2.1",
28 | "prettier": "2.5.1",
29 | "typescript": "4.5.5"
30 | },
31 | "dependencies": {
32 | "react": "17.0.2",
33 | "react-dom": "17.0.2",
34 | "react-router-dom": "^6.2.1"
35 | },
36 | "browserslist": [
37 | "last 2 Chrome versions"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/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 { render } from "react-dom";
2 | import SearchParams from "./SearchParams";
3 | import { StrictMode, useState } from "react";
4 | import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
5 | import Details from "./Details";
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 | render(, document.getElementById("root"));
28 |
--------------------------------------------------------------------------------
/typescript-5/src/Carousel.tsx:
--------------------------------------------------------------------------------
1 | import { Component, MouseEvent } 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() {
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 } from "react";
3 | import { Link, Navigate } from "react-router-dom";
4 |
5 | class ErrorBoundary extends Component {
6 | state = { hasError: false, redirect: false };
7 | static getDerivedStateFromError() {
8 | return { hasError: true };
9 | }
10 | componentDidCatch(error: Error, info: ErrorInfo): void {
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 | render() {
19 | if (this.state.redirect) {
20 | return ;
21 | } else if (this.state.hasError) {
22 | return (
23 |
24 | There was an error with this listing. Click here{" "}
25 | to back to the home page or wait five seconds.
26 |
27 | );
28 | }
29 |
30 | return this.props.children;
31 | }
32 | }
33 |
34 | export default ErrorBoundary;
35 |
--------------------------------------------------------------------------------
/typescript-5/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | const Modal: FunctionComponent = ({ children }) => {
5 | const elRef: MutableRefObject = useRef(null);
6 | if (!elRef.current) {
7 | elRef.current = document.createElement("div");
8 | }
9 |
10 | useEffect(() => {
11 | const modalRoot = document.getElementById("modal");
12 | if (!modalRoot || !elRef.current) {
13 | return;
14 | }
15 | modalRoot.appendChild(elRef.current);
16 | return () => {
17 | if (elRef.current) {
18 | modalRoot.removeChild(elRef.current);
19 | }
20 | }
21 | }, []);
22 |
23 | return createPortal({children}
, elRef.current);
24 | };
25 |
26 | export default Modal;
27 |
--------------------------------------------------------------------------------
/typescript-5/src/Pet.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | interface IProps {
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 from "./Pet";
3 | import { Pet as PetType } from "./APIResponsesTypes";
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.ts:
--------------------------------------------------------------------------------
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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/typescript-5/src/useBreedList.ts:
--------------------------------------------------------------------------------
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 | 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 |
--------------------------------------------------------------------------------