├── .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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 |

Learn useEffect By Example

3 | 4 | Get the hang of using useEffect like a pro, with useEffect By Example. 5 | 6 | 7 | 18 | 19 |

20 | 21 | Learn useEffect By Example 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 | --------------------------------------------------------------------------------