├── .gitignore
├── .prettierrc
├── 1-fetching-data
├── package.json
├── public
│ └── index.html
├── src
│ ├── after
│ │ └── App.js
│ ├── before
│ │ └── App.js
│ ├── index.js
│ └── styles.css
└── yarn.lock
├── 2-dependency-array
├── package.json
├── public
│ └── index.html
├── src
│ ├── after
│ │ └── App.js
│ ├── before
│ │ └── App.js
│ ├── index.js
│ └── styles.css
└── yarn.lock
├── 3-race-conditions
├── package.json
├── public
│ └── index.html
├── src
│ ├── after-abortcontroller
│ │ ├── App.js
│ │ └── DataDisplayer.js
│ ├── after-boolean
│ │ ├── App.js
│ │ └── DataDisplayer.js
│ ├── before
│ │ ├── App.js
│ │ └── DataDisplayer.js
│ ├── index.js
│ └── styles.css
└── yarn.lock
├── 4-usestate-and-useeffect
├── package.json
├── public
│ └── index.html
├── src
│ ├── App.js
│ ├── DataDisplayer.js
│ ├── index.js
│ └── styles.css
└── yarn.lock
├── CONTRIBUTING.md
├── LICENSE.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # macOS
107 | .DS_Store
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": false,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/1-fetching-data/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fetching-data-with-useeffect-1",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "react": "17.0.1",
9 | "react-dom": "17.0.1",
10 | "react-scripts": "4.0.1"
11 | },
12 | "devDependencies": {
13 | "typescript": "3.8.3"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/1-fetching-data/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
28 | You need to enable JavaScript to run this app.
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/1-fetching-data/src/after/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | export default function MyComponent() {
4 | const [data, setData] = useState(null)
5 | useEffect(() => {
6 | const fetchData = async () => {
7 | const response = await fetch('https://swapi.dev/api/people/1/')
8 | const newData = await response.json()
9 | setData(newData)
10 | }
11 |
12 | fetchData()
13 | }, [])
14 |
15 | return (
16 |
17 |
Hello!
18 | {data ?
{data.name}
: null}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/1-fetching-data/src/before/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 |
3 | export default function MyComponent() {
4 | useEffect(() => {
5 | const fetchData = async () => {
6 | const response = await fetch('https://swapi.dev/api/people/1/')
7 | const newData = await response.json()
8 | }
9 |
10 | fetchData()
11 | })
12 |
13 | return (
14 |
15 |
Hello!
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/1-fetching-data/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import AppBefore from './before/App'
5 | // import AppAfter from './after/App'
6 |
7 | const rootElement = document.getElementById('root')
8 | ReactDOM.render(
9 |
10 |
11 | {/* */}
12 | ,
13 | rootElement
14 | )
15 |
--------------------------------------------------------------------------------
/1-fetching-data/src/styles.css:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
--------------------------------------------------------------------------------
/2-dependency-array/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fetching-data-with-useeffect-and-deps-1",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "react": "17.0.1",
9 | "react-dom": "17.0.1",
10 | "react-scripts": "4.0.1"
11 | },
12 | "devDependencies": {
13 | "typescript": "3.8.3"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/2-dependency-array/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
28 | You need to enable JavaScript to run this app.
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/2-dependency-array/src/after/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | export default function MyComponent(props) {
4 | const [data, setData] = useState(null)
5 |
6 | useEffect(() => {
7 | const fetchData = async () => {
8 | const response = await fetch(`https://swapi.dev/api/people/${props.id}/`)
9 | const newData = await response.json()
10 | setData(newData)
11 | }
12 |
13 | fetchData()
14 | }, [props.id])
15 |
16 | return (
17 |
18 |
Hello!
19 | {data ?
{data.name}
: null}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/2-dependency-array/src/before/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | export default function MyComponent(props) {
4 | const [data, setData] = useState(null)
5 |
6 | useEffect(() => {
7 | const fetchData = async () => {
8 | const response = await fetch(`https://swapi.dev/api/people/${props.id}/`)
9 | const newData = await response.json()
10 | setData(newData)
11 | }
12 |
13 | fetchData()
14 | }, []) // <-- Note the squiggly warning
15 |
16 | return (
17 |
18 |
Hello!
19 | {data ?
{data.name}
: null}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/2-dependency-array/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import AppBefore from './before/App'
5 | // import AppAfter from './after/App'
6 |
7 | const rootElement = document.getElementById('root')
8 | ReactDOM.render(
9 |
10 |
11 | {/* */}
12 | ,
13 | rootElement
14 | )
15 |
--------------------------------------------------------------------------------
/2-dependency-array/src/styles.css:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
--------------------------------------------------------------------------------
/3-race-conditions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "beating-async-race-conditions-in-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "react": "17.0.1",
9 | "react-dom": "17.0.1",
10 | "react-scripts": "4.0.1"
11 | },
12 | "devDependencies": {
13 | "typescript": "3.8.3"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/3-race-conditions/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
28 | You need to enable JavaScript to run this app.
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/3-race-conditions/src/after-abortcontroller/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import DataDisplayer from './DataDisplayer'
3 |
4 | export default function App() {
5 | const [currentId, setCurrentId] = useState(null)
6 |
7 | const handleClick = () => {
8 | const idToFetch = Math.round(Math.random() * 80)
9 | setCurrentId(idToFetch)
10 | }
11 |
12 | return (
13 |
14 |
15 | {currentId ?
Latest requested ID: {currentId}
: null}
16 |
17 |
18 | Fetch data!
19 |
20 |
21 |
22 | {currentId ? : null}
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/3-race-conditions/src/after-abortcontroller/DataDisplayer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | export default function DataDisplayer(props) {
4 | const [data, setData] = useState(null)
5 | const [fetchedId, setFetchedId] = useState(null)
6 |
7 | useEffect(() => {
8 | const abortController = new AbortController()
9 |
10 | const fetchData = async () => {
11 | setTimeout(async () => {
12 | try {
13 | const response = await fetch(
14 | `https://swapi.dev/api/people/${props.id}/`,
15 | {
16 | signal: abortController.signal,
17 | }
18 | )
19 | const newData = await response.json()
20 |
21 | setFetchedId(props.id)
22 | setData(newData)
23 | } catch (error) {
24 | if (error.name === 'AbortError') {
25 | //Aborting a fetch throws an error
26 | //So we can't update state afterwards
27 | }
28 | // Handle other request errors here
29 | }
30 | }, Math.round(Math.random() * 12000))
31 | }
32 |
33 | fetchData()
34 | return () => {
35 | abortController.abort()
36 | }
37 | }, [props.id])
38 |
39 | return (
40 |
41 |
Request may take up to 12 seconds
42 |
43 | {data ? (
44 | <>
45 |
46 | Displaying Data for: {fetchedId}
47 |
48 |
Result: {data.name}
49 | >
50 | ) : null}
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/3-race-conditions/src/after-boolean/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import DataDisplayer from './DataDisplayer'
3 |
4 | export default function App() {
5 | const [currentId, setCurrentId] = useState(null)
6 |
7 | const handleClick = () => {
8 | const idToFetch = Math.round(Math.random() * 80)
9 | setCurrentId(idToFetch)
10 | }
11 |
12 | return (
13 |
14 |
15 | {currentId ?
Latest requested ID: {currentId}
: null}
16 |
17 |
18 | Fetch data!
19 |
20 |
21 |
22 | {currentId ? : null}
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/3-race-conditions/src/after-boolean/DataDisplayer.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useState } from 'react'
2 |
3 | export default function DataDisplayer(props) {
4 | const [data, setData] = useState(null)
5 | const [fetchedId, setFetchedId] = useState(null)
6 | // start helpers for visualising race conditions
7 | const [numReqs, setNumReqs] = useState(0)
8 | const increase = useCallback(() => setNumReqs((c) => c + 1), [])
9 | const decrease = useCallback(() => setNumReqs((c) => c - 1), [])
10 | // end helpers for visualising race conditions
11 | useEffect(() => {
12 | let active = true
13 |
14 | const fetchData = async () => {
15 | increase()
16 | setTimeout(async () => {
17 | const response = await fetch(
18 | `https://swapi.dev/api/people/${props.id}/`
19 | )
20 | const newData = await response.json()
21 | decrease()
22 | if (active) {
23 | setFetchedId(props.id)
24 | setData(newData)
25 | }
26 | }, Math.round(Math.random() * 12000))
27 | }
28 |
29 | fetchData()
30 | return () => {
31 | active = false
32 | }
33 | }, [props.id, increase, decrease])
34 |
35 | return (
36 |
37 |
Number of requests in flight: {numReqs} (may take up to 12 seconds)
38 |
39 | {data ? (
40 | <>
41 |
42 | Displaying Data for: {fetchedId}
43 |
44 |
Result: {data.name}
45 | >
46 | ) : null}
47 |
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/3-race-conditions/src/before/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import DataDisplayer from './DataDisplayer'
3 |
4 | export default function App() {
5 | const [currentId, setCurrentId] = useState(1)
6 |
7 | const handleClick = () => {
8 | const idToFetch = Math.round(Math.random() * 80)
9 | setCurrentId(idToFetch)
10 | }
11 |
12 | return (
13 |
14 |
15 |
Requesting ID: {currentId}
16 |
17 | Fetch data!
18 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/3-race-conditions/src/before/DataDisplayer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 |
3 | export default function DataDisplayer(props) {
4 | const [data, setData] = useState(null)
5 | const [fetchedId, setFetchedId] = useState(null)
6 |
7 | useEffect(() => {
8 | const fetchData = async () => {
9 | setTimeout(async () => {
10 | const response = await fetch(
11 | `https://swapi.dev/api/people/${props.id}/`
12 | )
13 | const newData = await response.json()
14 |
15 | setFetchedId(props.id)
16 | setData(newData)
17 | }, Math.round(Math.random() * 12000))
18 | }
19 |
20 | fetchData()
21 | }, [props.id])
22 |
23 | if (data) {
24 | return (
25 |
26 |
27 | Displaying Data for: {fetchedId}
28 |
29 |
{data.name}
30 |
31 | )
32 | } else {
33 | return null
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/3-race-conditions/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import AppBefore from './before/App'
5 | // import AppAfterBoolean from './after-boolean/App'
6 | // import AppAfterAbortController from './after-abortcontroller/App'
7 |
8 | const rootElement = document.getElementById('root')
9 | ReactDOM.render(
10 |
11 |
12 | {/* */}
13 | {/* */}
14 | ,
15 | rootElement
16 | )
17 |
--------------------------------------------------------------------------------
/3-race-conditions/src/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
--------------------------------------------------------------------------------
/4-usestate-and-useeffect/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "async-race-conditions-in-react-w-count",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "react": "17.0.1",
9 | "react-dom": "17.0.1",
10 | "react-scripts": "4.0.1"
11 | },
12 | "devDependencies": {
13 | "typescript": "3.8.3"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/4-usestate-and-useeffect/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
28 | You need to enable JavaScript to run this app.
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/4-usestate-and-useeffect/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from "react";
2 | import DataDisplayer from "./DataDisplayer";
3 |
4 | export default function App() {
5 | const [currentId, setCurrentId] = useState(1);
6 | const [count, setCount] = useState(0);
7 |
8 | const increaseCount = useCallback(
9 | () => setCount((prevCount) => prevCount + 1),
10 | []
11 | );
12 |
13 | const decreaseCount = useCallback(
14 | () => setCount((prevCount) => prevCount - 1),
15 | []
16 | );
17 |
18 | const handleClick = () => {
19 | const idToFetch = Math.round(Math.random() * 80);
20 | setCurrentId(idToFetch);
21 | };
22 |
23 | return (
24 |
25 |
26 |
Number of active requests: {count}
27 |
Requesting ID: {currentId}
28 |
29 | Fetch data!
30 |
31 |
32 |
33 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/4-usestate-and-useeffect/src/DataDisplayer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 |
3 | export default function DataDisplayer(props) {
4 | const { increaseCount, decreaseCount } = props;
5 | const [data, setData] = useState(null);
6 | const [fetchedId, setFetchedId] = useState(null);
7 |
8 | useEffect(() => {
9 | const fetchData = async () => {
10 | increaseCount();
11 | setTimeout(async () => {
12 | const response = await fetch(
13 | `https://swapi.dev/api/people/${props.id}/`
14 | );
15 | const newData = await response.json();
16 |
17 | setFetchedId(props.id);
18 | setData(newData);
19 | decreaseCount();
20 | }, Math.round(Math.random() * 12000));
21 | };
22 |
23 | fetchData();
24 | }, [props.id, increaseCount, decreaseCount]);
25 |
26 | if (data) {
27 | return (
28 |
29 |
30 | Displaying Data for: {fetchedId}
31 |
32 |
{data.name}
33 |
34 | );
35 | } else {
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/4-usestate-and-useeffect/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | rootElement
12 | );
13 |
--------------------------------------------------------------------------------
/4-usestate-and-useeffect/src/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for being willing to contribute!
4 |
5 | **Working on your first Pull Request?** You can learn how from this _free_
6 | series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
7 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | This material is available for private, non-commercial use under the
2 | [GPL version 3](http://www.gnu.org/licenses/gpl-3.0-standalone.html). If you
3 | would like to use this material to conduct your own workshop, please contact me
4 | at max@maxrozen.com
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Get the hang of using useEffect like a pro, with useEffect By Example.
5 |
6 |
7 |
8 |
9 | You'll learn how to use useEffect to fetch data, and avoid pesky race conditions.
10 |
11 |
12 | Take the guesswork out of using the dependency array, and keep the eslint-plugin happy.
13 |
14 |
15 | Prevent infinite re-renders through the use of useCallback and useMemo.
16 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | [![GPL 3.0 License][license-badge]][license]
35 |
36 |
37 |
38 | ## System Requirements
39 |
40 | - [git][git] v2.13 or greater
41 | - [NodeJS][node] `^10.13 || 12 || 14 || 15`
42 | - [npm][npm] v6 or greater
43 |
44 | All of these must be available in your `PATH`. To verify things are set up
45 | properly, you can run this:
46 |
47 | ```shell
48 | git --version
49 | node --version
50 | npm --version
51 | ```
52 |
53 | If you have trouble with any of these, learn more about the PATH environment
54 | variable and how to fix it here for [windows][win-path] or
55 | [mac/linux][mac-path].
56 |
57 | ## Setting up
58 | For each chapter, you'll need to run:
59 |
60 | ```shell
61 | npm install
62 | ```
63 |
64 | To download the dependencies.
65 |
66 | ## Running the apps
67 |
68 | To get the app up and running (and really see if it worked), run:
69 |
70 | ```shell
71 | npm start
72 | ```
73 |
74 | This should start up your browser. If you're familiar, this is a standard
75 | [react-scripts](https://create-react-app.dev/) application.
76 |
77 |
78 |
79 | ### Examples
80 |
81 | - `1-fetching-data/src/before/App.js`: Fetching data with useEffect
82 | - `1-fetching-data/src/after/App.js`: Fetching & displaying data with useEffect
83 | - `2-dependency-array/src/before/App.js`: Demonstrating the lack of a dependency
84 | - `2-dependency-array/src/after/App.js`: Adding a dependency to allow useEffect to re-fire
85 | - `3-race-conditions/src/before/App.js`: Demonstrating race conditions with an async useEffect
86 | - `3-race-conditions/src/after-boolean/App.js`: Fixing race conditions using a clean-up function, and a boolean
87 | - `3-race-conditions/src/after-abortcontroller/App.js`: Fixing race conditions using a clean-up function, and an [AbortController][mdn]
88 | - `4-usestate-and-useeffect/src/App.js`: Demonstrating use of useState, useCallback, and useEffect
89 |
90 |
91 | [npm]: https://www.npmjs.com/
92 | [node]: https://nodejs.org
93 | [git]: https://git-scm.com/
94 | [license-badge]: https://img.shields.io/badge/license-GPL%203.0%20License-blue.svg?style=flat-square
95 | [license]: https://github.com/rozenmd/useEffect-examples/blob/main/LICENSE.md
96 | [win-path]: https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/
97 | [mac-path]: http://stackoverflow.com/a/24322978/971592
98 | [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
99 |
100 |
--------------------------------------------------------------------------------