├── .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 |
24 |
{dog.description}
25 |
26 |
Temperament
27 |
28 | {dog.temperament.map(t => (
29 | {t}
30 | ))}
31 |
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 |
23 |
{dog.description}
24 |
25 |
Temperament
26 |
27 | {dog.temperament.map(t => (
28 | {t}
29 | ))}
30 |
31 |
32 | {dog.related.length ? (
33 |
34 |
Related Dogs
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 |
24 |
{dog.description}
25 |
26 |
Temperament
27 |
28 | {dog.temperament.map(t => (
29 | {t}
30 | ))}
31 |
32 |
33 | {dog.related.length ? (
34 |
35 |
Related Dogs
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 |
34 | You need to enable JavaScript to run this app.
35 |
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 | setSelection(index)}
43 | >
44 | {content}
45 |
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 |
--------------------------------------------------------------------------------