├── .gitignore ├── dogs-and-dependencies ├── .eslintignore ├── .prettireignore ├── src │ ├── img │ │ ├── lab.jpg │ │ ├── beagle.jpg │ │ ├── poodle.jpg │ │ ├── bernese.jpg │ │ ├── cute-dogs.jpg │ │ └── bernedoodle.jpg │ ├── bg.js │ ├── styles.css │ ├── 1-hidden-bug.js │ ├── 3-fixed-version.js │ ├── 2-revealed-bug.js │ ├── index.js │ └── dogs.js ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── .prettierrc ├── .gitignore └── package.json ├── slides ├── 01.mdx ├── 02.0.jpg ├── 02.1.png ├── 06.1.png ├── 10.mdx ├── 09.mdx ├── 04.mdx ├── 03.mdx ├── 00.mdx ├── 07.mdx ├── 05.mdx ├── 08.mdx └── 06.0.mdx ├── .prettierrc └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /dogs-and-dependencies/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | public 5 | .docz 6 | -------------------------------------------------------------------------------- /slides/01.mdx: -------------------------------------------------------------------------------- 1 | # Please Stand! ️️🏋 2 | 3 | > If you want to and you're physically able 💛 ♿️ 4 | -------------------------------------------------------------------------------- /slides/02.0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/slides/02.0.jpg -------------------------------------------------------------------------------- /slides/02.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/slides/02.1.png -------------------------------------------------------------------------------- /slides/06.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/slides/06.1.png -------------------------------------------------------------------------------- /dogs-and-dependencies/.prettireignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | coverage 4 | public 5 | .docz 6 | package.json 7 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/img/lab.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/dogs-and-dependencies/src/img/lab.jpg -------------------------------------------------------------------------------- /dogs-and-dependencies/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/dogs-and-dependencies/public/favicon.ico -------------------------------------------------------------------------------- /dogs-and-dependencies/src/img/beagle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/dogs-and-dependencies/src/img/beagle.jpg -------------------------------------------------------------------------------- /dogs-and-dependencies/src/img/poodle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/dogs-and-dependencies/src/img/poodle.jpg -------------------------------------------------------------------------------- /dogs-and-dependencies/src/img/bernese.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/dogs-and-dependencies/src/img/bernese.jpg -------------------------------------------------------------------------------- /dogs-and-dependencies/src/img/cute-dogs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/dogs-and-dependencies/src/img/cute-dogs.jpg -------------------------------------------------------------------------------- /dogs-and-dependencies/src/img/bernedoodle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentcdodds/react-hooks-pitfalls/HEAD/dogs-and-dependencies/src/img/bernedoodle.jpg -------------------------------------------------------------------------------- /slides/10.mdx: -------------------------------------------------------------------------------- 1 | # Thank you! 2 | 3 | 👋 4 | 5 | 🐦 @kentcdodds 6 | 7 | 💌 https://kentcdodds.com/subscribe 8 | 9 | Slides: https://github.com/kentcdodds/react-hooks-pitfalls 10 | -------------------------------------------------------------------------------- /dogs-and-dependencies/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": false, 9 | "jsxBracketSameLine": false 10 | } 11 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/bg.js: -------------------------------------------------------------------------------- 1 | import cuteDogs from './img/cute-dogs.jpg' 2 | 3 | document.body.style.backgroundImage = `url('${cuteDogs}')` 4 | document.body.style.backgroundColor = 'rgba(255, 255, 255, 0.9)' 5 | document.body.style.backgroundBlendMode = 'color' 6 | -------------------------------------------------------------------------------- /slides/09.mdx: -------------------------------------------------------------------------------- 1 | # In Review 2 | 3 | 1. Read the docs and the FAQ 📚 4 | 2. Install, use, and follow the ESLint plugin 👨‍🏫 5 | 3. Think about synchronizing side effects to state 🔄 6 | 4. Profile your app, _then_ optimize 🏎💨 7 | 5. Avoid testing implementation details 🔬 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": false, 4 | "jsxBracketSameLine": false, 5 | "printWidth": 80, 6 | "proseWrap": "always", 7 | "semi": false, 8 | "singleQuote": true, 9 | "tabWidth": 2, 10 | "trailingComma": "all", 11 | "useTabs": false 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Hook Pitfalls 2 | 3 | This is the slides and code examples for my talk "React Hook Pitfalls" 4 | 5 | ## License 6 | 7 | This material is available for private, non-commercial use under the 8 | [GPL version 3](http://www.gnu.org/licenses/gpl-3.0-standalone.html). If you 9 | would like to use this material to conduct your own training, please contact me 10 | at kent@doddsfamily.us 11 | -------------------------------------------------------------------------------- /dogs-and-dependencies/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } -------------------------------------------------------------------------------- /dogs-and-dependencies/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | .docz -------------------------------------------------------------------------------- /slides/04.mdx: -------------------------------------------------------------------------------- 1 | # Pitfall 1: Starting without a good foundation 2 | 3 | Read. The. Docs: https://reactjs.org/hooks 4 | 5 | Also: 6 | 7 | - 🥚 https://kcd.im/hooks-playlist 8 | - 🏚 ➡ 🏠 https://kcd.im/refactor-react 9 | - 💻 https://kentcdodds.com/workshops 10 | - Other educators/courses 11 | - 🎁⚛🎁 coming soon! Sign up at https://kentcdodds.com/subscribe to be notified 12 | 13 | --- 14 | 15 | **Read the docs and the FAQ 📚** 16 | -------------------------------------------------------------------------------- /slides/03.mdx: -------------------------------------------------------------------------------- 1 | # What this talk is 2 | 3 | - Exploring common mistakes people make when learning/adopting React Hooks 4 | - Help make you better at using React Hooks 5 | 6 | # What this talk is not 7 | 8 | - Convincing you that you should use hooks 9 | - Convincing you that you should not use hooks 10 | 11 | # What font/theme is that? 12 | 13 | - Dank Mono 14 | - Night Owl 15 | - VSCode 16 | 17 | # Why are you using your editor for slides? 18 | 19 | I like this idea: https://staltz.com/your-ide-as-a-presentation-tool.html 20 | -------------------------------------------------------------------------------- /slides/00.mdx: -------------------------------------------------------------------------------- 1 | # React Hook Pitfalls 2 | 3 | > The hooks honeymoon phase is over 4 | 5 | 👋 I'm Kent C. Dodds 6 | 7 | - Slides https://github.com/kentcdodds/react-hooks-pitfalls 8 | - 🏡 Utah 9 | - 👩 👧 👦 👦 👦 🐕 10 | - 🏢 kentcdodds.com 11 | - 🐦/🐙 @kentcdodds 12 | - 🏆 testingjavascript.com 13 | - 💻 kcd.im/workshops 14 | - 🎙 kcd.im/podcast 15 | - 🥚 kcd.im/egghead 16 | - 🥋 kcd.im/fem 17 | - 💌 kcd.im/news 18 | - 📝 kcd.im/blog 19 | - 📺 kcd.im/devtips 20 | - 👨‍💻 kcd.im/coding 21 | - 📽 kcd.im/youtube 22 | - ❓ kcd.im/ama 23 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Comfortaa', cursive; 3 | font-size: 18px; 4 | text-shadow: 0px 0px 0.5px rgba(255, 255, 255, 0.6); 5 | } 6 | 7 | a { 8 | color: #59567e; 9 | text-decoration: none; 10 | } 11 | a:hover, 12 | a:focus, 13 | a:active { 14 | color: #4e6177; 15 | text-decoration: underline; 16 | } 17 | 18 | hr { 19 | margin-top: 25px; 20 | margin-bottom: 25px; 21 | } 22 | 23 | .nav-link { 24 | padding: 0; 25 | border: none; 26 | background-color: transparent; 27 | font-size: inherit; 28 | color: #59567e; 29 | cursor: pointer; 30 | } 31 | .nav-link:hover, 32 | .nav-link:focus, 33 | .nav-link:active { 34 | color: #4e6177; 35 | text-decoration: underline; 36 | } 37 | 38 | .nav-link::before { 39 | content: '🐶'; 40 | margin-right: 4px; 41 | opacity: 0; 42 | transition: opacity 0.2s; 43 | } 44 | .nav-link.active::before { 45 | opacity: 1; 46 | } 47 | -------------------------------------------------------------------------------- /dogs-and-dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dogs-and-dependencies", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reach/router": "1.2.1", 7 | "react": "^16.8.6", 8 | "react-dom": "^16.8.6", 9 | "react-scripts": "^3.0.0" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "format": "prettier --write \"**/*.+(js|json|css|md|mdx|html)\"" 16 | }, 17 | "eslintConfig": { 18 | "extends": "react-app" 19 | }, 20 | "husky": { 21 | "hooks": { 22 | "pre-commit": "lint-staged && npm run build" 23 | } 24 | }, 25 | "browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"], 26 | "devDependencies": { 27 | "prettier": "^1.15.2", 28 | "husky": "^1.1.4", 29 | "lint-staged": "^8.0.4" 30 | }, 31 | "keywords": [], 32 | "description": "This is a demo for https://kcd.im/pitfalls" 33 | } 34 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/1-hidden-bug.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from '@reach/router' 3 | import {getDog} from './dogs' 4 | 5 | function DogInfo({dogId}) { 6 | const [dog, setDog] = React.useState(null) 7 | 8 | React.useEffect(() => { 9 | getDog(dogId).then(d => setDog(d)) 10 | // eslint-disable-next-line react-hooks/exhaustive-deps 11 | }, []) // 😱 12 | 13 | if (!dog) { 14 | return null 15 | } 16 | 17 | return ( 18 |
19 |
20 | Return to list 21 |
22 |

{dog.name}

23 | {dog.name} 24 |

{dog.description}

25 |
26 | 27 | 32 |
33 |
34 | ) 35 | } 36 | 37 | export default DogInfo 38 | -------------------------------------------------------------------------------- /slides/07.mdx: -------------------------------------------------------------------------------- 1 | # Pitfall 4: Overthinking performance 2 | 3 | Does this worry you? 4 | 5 | ```jsx 6 | function YoYoScreen() { 7 | const [yoyo, dispatch] = React.useContext(YoYoContext) 8 | 9 | const handleUpdate = updates => dispatch({type: 'update', updates}) 10 | 11 | return 12 | } 13 | ``` 14 | 15 | Is this better? 16 | 17 | ```jsx 18 | function YoYoScreen() { 19 | const [yoyo, dispatch] = React.useContext(YoYoContext) 20 | 21 | const handleUpdate = React.useCallback( 22 | updates => dispatch({type: 'update', updates}), 23 | [], 24 | ) 25 | 26 | return 27 | } 28 | ``` 29 | 30 | Maybe it is, maybe it isn't... 31 | 32 | **Takeaway**: Be considerate of performance, but also be considerate of your 33 | code complexity. _Measure first._ 34 | 35 | [useMemo and useCallback](https://kentcdodds.com/blog/usememo-and-usecallback) 36 | 37 | --- 38 | 39 | **Profile your app, _then_ optimize 🏎💨** 40 | -------------------------------------------------------------------------------- /slides/05.mdx: -------------------------------------------------------------------------------- 1 | # Pitfall 2: Not using (or ignoring) the ESLint plugin 2 | 3 | https://www.npmjs.com/package/eslint-plugin-react-hooks 4 | 5 | ```jsx 6 | // react-hooks/rules-of-hooks 7 | import React from 'react' 8 | 9 | function Foo({bar}) { 10 | if (bar) { 11 | React.useEffect() // <-- that's an error 12 | // React Hook "React.useEffect" is called conditionally. React Hooks must be 13 | // called in the exact same order in every component render. 14 | } 15 | } 16 | ``` 17 | 18 | ```jsx 19 | // react-hooks/exhaustive-deps 20 | import React from 'react' 21 | 22 | function Foo({bar}) { 23 | React.useEffect(() => { 24 | bar(123) 25 | }, []) // <-- that's an error 26 | // React Hook React.useEffect has a missing dependency: 'bar'. Either include 27 | // it or remove the dependency array. If 'bar' changes too often, find the 28 | // parent component that defines it and wrap that definition in useCallback. 29 | } 30 | ``` 31 | 32 | Demo time: https://codesandbox.io/s/dogs-and-dependencies-fdo3b 33 | 34 | --- 35 | 36 | **Install, use, and follow the ESLint plugin 👨‍🏫** 37 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/3-fixed-version.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from '@reach/router' 3 | import {getDog} from './dogs' 4 | 5 | function DogInfo({dogId}) { 6 | const [dog, setDog] = React.useState(null) 7 | 8 | React.useEffect(() => { 9 | getDog(dogId).then(d => setDog(d)) 10 | }, [dogId]) 11 | 12 | if (!dog) { 13 | return null 14 | } 15 | 16 | return ( 17 |
18 |
19 | Return to list 20 |
21 |

{dog.name}

22 | {dog.name} 23 |

{dog.description}

24 |
25 | 26 |
    27 | {dog.temperament.map(t => ( 28 |
  • {t}
  • 29 | ))} 30 |
31 |
32 | {dog.related.length ? ( 33 |
34 | 35 | 42 |
43 | ) : null} 44 |
45 | ) 46 | } 47 | 48 | export default DogInfo 49 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/2-revealed-bug.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from '@reach/router' 3 | import {getDog} from './dogs' 4 | 5 | function DogInfo({dogId}) { 6 | const [dog, setDog] = React.useState(null) 7 | 8 | React.useEffect(() => { 9 | getDog(dogId).then(d => setDog(d)) 10 | // eslint-disable-next-line react-hooks/exhaustive-deps 11 | }, []) // 😱 12 | 13 | if (!dog) { 14 | return null 15 | } 16 | 17 | return ( 18 |
19 |
20 | Return to list 21 |
22 |

{dog.name}

23 | {dog.name} 24 |

{dog.description}

25 |
26 | 27 |
    28 | {dog.temperament.map(t => ( 29 |
  • {t}
  • 30 | ))} 31 |
32 |
33 | {dog.related.length ? ( 34 |
35 | 36 | 43 |
44 | ) : null} 45 |
46 | ) 47 | } 48 | 49 | export default DogInfo 50 | -------------------------------------------------------------------------------- /slides/08.mdx: -------------------------------------------------------------------------------- 1 | # Pitfall 5: Testing Implementation Details 2 | 3 | You might have a problem refactoring to hooks: 4 | 5 | ```jsx 6 | test('setOpenIndex sets the open index state properly', () => { 7 | // using enzyme 8 | const wrapper = mount() 9 | expect(wrapper.state('openIndex')).toBe(0) 10 | wrapper.instance().setOpenIndex(1) 11 | expect(wrapper.state('openIndex')).toBe(1) 12 | }) 13 | ``` 14 | 15 | Hooks are an Implementation Detail. 16 | 17 | This test works whether you're using hooks or not. 18 | 19 | ```jsx 20 | test('can open accordion items to see the contents', () => { 21 | const hats = {title: 'Favorite Hats', contents: 'Fedoras are classy'} 22 | const footware = { 23 | title: 'Favorite Footware', 24 | contents: 'Flipflops are the best', 25 | } 26 | const items = [hats, footware] 27 | // using React Testing Library 28 | const {queryByText} = render() 29 | expect(queryByText(hats.contents)).toBeInTheDocument() 30 | expect(queryByText(footware.contents)).toBeNull() 31 | 32 | fireEvent.click(queryByText(footware.title)) 33 | 34 | expect(queryByText(footware.contents)).toBeInTheDocument() 35 | expect(queryByText(hats.contents)).toBeNull() 36 | }) 37 | ``` 38 | 39 | > The more your tests resemble the way your software is used, the more 40 | > confidence they can give you. 41 | > [Kent C. Dodds 👋](https://twitter.com/kentcdodds/status/977018512689455106) 42 | 43 | [React Hooks: What's going to happen to my tests?](https://kentcdodds.com/blog/react-hooks-whats-going-to-happen-to-my-tests) 44 | [Testing Implementation Details](https://kentcdodds.com/blog/testing-implementation-details) 45 | [How to know what to test](https://kentcdodds.com/blog/how-to-know-what-to-test) 46 | [🏆 TestingJavaScript.com](https://testingjavascript.com) 47 | 48 | --- 49 | 50 | **Avoid testing implementation details 🔬** 51 | -------------------------------------------------------------------------------- /dogs-and-dependencies/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 20 | 29 | React App 30 | 31 | 32 | 33 | 36 |
37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/index.js: -------------------------------------------------------------------------------- 1 | import './bg' 2 | import './styles.css' 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import {Router, Link} from '@reach/router' 6 | import HiddenBug from './1-hidden-bug' 7 | import RevealedBug from './2-revealed-bug' 8 | import FixedVersion from './3-fixed-version' 9 | import {getDogs} from './dogs' 10 | 11 | function DogList() { 12 | const [dogs, setDogs] = React.useState(null) 13 | React.useEffect(() => { 14 | getDogs().then(d => setDogs(d)) 15 | }, []) 16 | if (!dogs) { 17 | return null 18 | } 19 | return ( 20 |
21 |

Pick a dog

22 |
    23 | {dogs.map(d => ( 24 |
  • 25 | {d.name} 26 |
  • 27 | ))} 28 |
29 |
30 | ) 31 | } 32 | 33 | const Apps = [HiddenBug, RevealedBug, FixedVersion] 34 | 35 | function App() { 36 | const [selection, setSelection] = React.useState(0) 37 | const DogInfo = Apps[selection] 38 | const renderNavLink = (index, content) => ( 39 |
  • 40 | 46 |
  • 47 | ) 48 | return ( 49 |
    50 |

    Dogs and Dependencies

    51 |
      52 | {renderNavLink(0, 'Hidden Bug')} 53 | {renderNavLink(1, 'Revealed Bug')} 54 | {renderNavLink(2, 'FixedVersion')} 55 |
    56 |
    57 | {DogInfo ? ( 58 | 59 | 60 | 61 | 62 | ) : ( 63 | 'Choose a version' 64 | )} 65 |
    66 | ) 67 | } 68 | 69 | ReactDOM.render(, document.getElementById('root')) 70 | -------------------------------------------------------------------------------- /dogs-and-dependencies/src/dogs.js: -------------------------------------------------------------------------------- 1 | import poodle from './img/poodle.jpg' 2 | import bernese from './img/bernese.jpg' 3 | import lab from './img/lab.jpg' 4 | import beagle from './img/beagle.jpg' 5 | import bernedoodle from './img/bernedoodle.jpg' 6 | 7 | const dogs = [ 8 | { 9 | id: 'dog-1', 10 | name: 'Poodle', 11 | img: poodle, 12 | description: 13 | 'Poodles are a group of formal dog breeds, the Standard Poodle, Miniature Poodle and Toy Poodle. The origin of the breed is still discussed, with a prominent dispute over whether the poodle descends from Germany as a type of water dog, or from the French Barbet.', 14 | temperament: [ 15 | 'Intelligent', 16 | 'Active', 17 | 'Alert', 18 | 'Faithful', 19 | 'Trainable', 20 | 'Instinctual', 21 | ], 22 | related: [{name: 'Bernedoodle', id: 'dog-5'}], 23 | }, 24 | { 25 | id: 'dog-2', 26 | name: 'Bernese Mountain Dog', 27 | img: bernese, 28 | description: 29 | 'The Bernese Mountain Dog is a large-sized breed of dog, one of the four breeds of Sennenhund-type dogs from the Swiss Alps. Bred from crosses of Mastiffs and guard-type breeds, Bernese Mountain Dogs were brought to Switzerland by the Romans 2,000 years ago.', 30 | temperament: ['Affectionate', 'Intelligent', 'Loyal', 'Faithful'], 31 | related: [{name: 'Bernedoodle', id: 'dog-5'}], 32 | }, 33 | { 34 | id: 'dog-3', 35 | name: 'Labrador Retriever', 36 | img: lab, 37 | description: 38 | 'The Labrador Retriever, or just Labrador, is a large type of retriever-gun dog. The Labrador is one of the most popular breeds of dog in Canada, the United Kingdom and the United States.', 39 | temperament: [ 40 | 'Intelligent', 41 | 'Even Tempered', 42 | 'Kind', 43 | 'Agile', 44 | 'Outgoing', 45 | 'Trusting', 46 | 'Gentle', 47 | ], 48 | related: [], 49 | }, 50 | { 51 | id: 'dog-4', 52 | name: 'Beagle', 53 | img: beagle, 54 | description: 55 | 'The beagle is a breed of small hound that is similar in appearance to the much larger foxhound. The beagle is a scent hound, developed primarily for hunting hare.', 56 | temperament: [ 57 | 'Intelligent', 58 | 'Even Tempered', 59 | 'Determined', 60 | 'Amiable', 61 | 'Excitable', 62 | 'Gentle', 63 | ], 64 | related: [], 65 | }, 66 | { 67 | id: 'dog-5', 68 | name: 'Bernedoodle', 69 | img: bernedoodle, 70 | description: 'The best dog ever.', 71 | temperament: ['All', 'The', 'Good', 'Things'], 72 | related: [ 73 | {id: 'dog-1', name: 'Poodle'}, 74 | {id: 'dog-2', name: 'Bernese Mountain Dog'}, 75 | ], 76 | }, 77 | ] 78 | 79 | async function getDog(dogId) { 80 | return dogs.find(({id}) => id === dogId) 81 | } 82 | 83 | async function getDogs() { 84 | return dogs.map(({id, name}) => ({id, name})) 85 | } 86 | 87 | export {getDog, getDogs} 88 | -------------------------------------------------------------------------------- /slides/06.0.mdx: -------------------------------------------------------------------------------- 1 | # Pitfall 3: Thinking in Lifecycles 2 | 3 | ```jsx 4 | class DogInfo extends React.Component { 5 | controller = null 6 | state = {dog: null} 7 | // we'll ignore error/loading states for brevity 8 | fetchDog() { 9 | if (this.controller) { 10 | this.controller.abort() 11 | } 12 | this.controller = new AbortController() 13 | getDog(this.props.dogId, {signal: this.controller.signal}) 14 | .then(dog => { 15 | this.setState({dog}) 16 | }) 17 | .catch(e => { 18 | // handle the error 19 | }) 20 | } 21 | componentDidMount() { 22 | this.fetchDog() 23 | } 24 | componentDidUpdate(prevProps) { 25 | if (prevProps.dogId !== this.props.dogId) { 26 | this.fetchDog() 27 | } 28 | } 29 | componentWillUnmount() { 30 | if (this.controller) { 31 | this.controller.abort() 32 | } 33 | } 34 | render() { 35 | return
    {/* render dog's info */}
    36 | } 37 | } 38 | ``` 39 | 40 | Let's refactor this to hooks! 41 | 42 | ```jsx 43 | function DogInfo({dogId}) { 44 | const controllerRef = React.useRef(null) 45 | const [dog, setDog] = React.useState(null) 46 | function fetchDog() { 47 | if (controllerRef.current) { 48 | controllerRef.current.abort() 49 | } 50 | controllerRef.current = new AbortController() 51 | getDog(dogId, {signal: controllerRef.current.signal}) 52 | .then(d => setDog(d)) 53 | .catch(e => { 54 | // handle the error 55 | }) 56 | } 57 | 58 | // didMount 59 | React.useEffect(() => { 60 | fetchDog() 61 | // eslint-disable-next-line react-hooks/exhaustive-deps 62 | }, []) 63 | 64 | // didUpdate 65 | const previousDogId = usePrevious(dogId) 66 | useUpdate(() => { 67 | if (previousDogId !== dogId) { 68 | fetchDog() 69 | } 70 | }) 71 | 72 | // willUnmount 73 | React.useEffect(() => { 74 | return () => { 75 | if (controllerRef.current) { 76 | controllerRef.current.abort() 77 | } 78 | } 79 | }, []) 80 | 81 | return
    {/* render dog's info */}
    82 | } 83 | ``` 84 | 85 | 😬 Huh... And... why do we need hooks again? 86 | 87 | Effects give you a mechanism for: 88 | 89 | **synchronizing the state of the world with the state of your component** 90 | 91 | So what if we forgot about lifecycles, and thought instead about synchronizing 92 | side-effects with state? 93 | 94 | ```jsx 95 | function DogInfo({dogId}) { 96 | const [dog, setDog] = React.useState(null) 97 | React.useEffect(() => { 98 | const controller = new AbortController() 99 | getDog(dogId, {signal: controller.signal}) 100 | .then(d => setDog(d)) 101 | .catch(e => { 102 | // handle the error 103 | }) 104 | return () => controller.abort() 105 | }, [dogId]) 106 | 107 | return
    {/* render dog's info */}
    108 | } 109 | ``` 110 | 111 | > The question is not "when does this effect run" the question is "with which 112 | > state does this effect synchronize with" 113 | > 114 | > ``` 115 | > useEffect(fn) // all state 116 | > useEffect(fn, []) // no state 117 | > useEffect(fn, [these, states]) 118 | > ``` 119 | > 120 | > - [Ryan Florence](https://twitter.com/ryanflorence/status/1125041041063665666) 121 | 122 | --- 123 | 124 | **Think about synchronizing side effects to state 🔄** 125 | --------------------------------------------------------------------------------