diff --git a/.gitignore b/.gitignore
index e050326c..9eaee462 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ build
.eslintcache
/playwright-report
/test-results
+/playground
diff --git a/exercises/README.mdx b/exercises/README.mdx
index bdf9577a..35814365 100644
--- a/exercises/README.mdx
+++ b/exercises/README.mdx
@@ -1,5 +1,114 @@
# Advanced React Patterns 🤯
-Welcome to the workshop
+> Some sweeeeeeeet patterns 🍭
-TODO: write more stuff
+👨💼 Hello, my name is Peter the Product Manager. I'm here to help you get
+oriented and to give you your assignments for the day!
+
+Today, you'll learn important testing skills for web app development including:
+
+- Latest Ref
+- Composition
+- Compound Components
+- Prop Collections and Getters
+- State Initializer
+- State Reducer
+- Control Props
+
+## Meet the instructor
+
+👨🏫 Hi, I'm your instructor [Kent C. Dodds](https://kentcdodds.com). Here's some
+stuff about me:
+
+- 🏡 Utah
+- 👩 👧 👦 👦 👦 🐕
+- 🏢 [kentcdodds.com](https://kentcdodds.com)
+- [🐦](https://twitter.com/kentcdodds)/[🐙](https://github.com/kentcdodds)
+ @kentcdodds
+- 🌌 [EpicWeb.dev](https://EpicWeb.dev)
+- 🚀 [EpicReact.dev](https://EpicReact.dev)
+- 🏆 [testingjavascript.com](https://testingjavascript.com)
+- 🥚 [kcd.im/egghead](https://kcd.im/egghead)
+- 🥋 [kcd.im/fem](https://kcd.im/fem)
+- 💌 [kcd.im/news](https://kcd.im/news)
+- 📝 [kcd.im/blog](https://kcd.im/blog)
+- 📽 [kcd.im/youtube](https://kcd.im/youtube)
+- 🎙 [kcd.im/calls](https://kcd.im/calls)
+
+## What this workshop is
+
+- Lots of exercises
+
+## What this workshop is not
+
+- Solo
+- Lecture
+
+## Logistics
+
+### Schedule
+
+- 😴 Logistics
+- 💪 Latest Ref
+- 💪 Composition
+- 😴 10 Minutes
+- 💪 Compound Components
+- 💪 Prop Collections and Getters
+- 🌮 60 Minutes
+- 💪 State Initializer
+- 💪 State Reducer
+- 😴 10 Minutes
+- 💪 Control Props
+- 😴 10 Minutes
+
+### Scripts
+
+- `npm run start`
+
+### Asking Questions
+
+Please do ask! Interrupt me. If you have an unrelated question, please ask on
+[office hours](https://kcd.im/office-hours) or
+[the call Kent podcast](https://kcd.im/calls).
+
+### Zoom
+
+(For a live remote workshop)
+
+- Help us make this more human by keeping your video on if possible
+- Keep microphone muted unless speaking
+- Breakout rooms
+
+### Exercises
+
+You'll spend all of your time in the `./playground` directory. You'll find the
+problem and solution code in the `./exercises` directory, but you shouldn't need
+to go there unless you're curious. Here's how that is structured:
+
+- `exercises/*.*/README.mdx`: Background information
+- `exercises/*.*/*.problem.*/README.*.mdx`: Problem Instructions
+- `exercises/*.*/*.problem.*`: Exercises with Emoji helpers.
+- `exercises/*.*/*.solution.*`: Solved version
+
+### Emoji
+
+- **Kody the Koala** 🐨 "Do this"
+- **Matthew the Muscle** 💪 "Exercise"
+- **Chuck the Checkered Flag** 🏁 "Final"
+- **Marty the Money Bag** 💰 "Here's a hint"
+- **Olivia the Owl** 🦉 "Pro-tip"
+- **Dominic the Document** 📜 "Docs links"
+- **Barry the Bomb** 💣 "Remove this code"
+- **Peter the Product Manager** 👨💼 "Story time"
+- **Alfred the Alert** 🚨 "Extra helpful in test errors"
+- **Kellie the Co-worker** 🧝♀️ "Your co-worker"
+
+### Workshop Feedback
+
+Each exercise has an Elaboration and Feedback link. Please fill that out after
+the exercise and instruction.
+
+At the end of the workshop, there's [a survey](/finished). Please fill that out
+as well.
+
+It's a big job and there's lots to do, so, let's get started!
diff --git a/package-lock.json b/package-lock.json
index 65424032..e1d20516 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,19 +10,19 @@
"hasInstallScript": true,
"license": "GPL-3.0-only",
"dependencies": {
- "@kentcdodds/workshop-app": "^1.35.0",
+ "@kentcdodds/workshop-app": "^1.36.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@playwright/test": "^1.33.0",
- "@testing-library/dom": "^9.2.0",
+ "@testing-library/dom": "^9.3.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
- "@types/react": "^18.2.0",
- "@types/react-dom": "^18.2.1",
+ "@types/react": "^18.2.6",
+ "@types/react-dom": "^18.2.4",
"cross-env": "^7.0.3",
- "eslint": "^8.39.0",
+ "eslint": "^8.40.0",
"eslint-config-react-app": "^7.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
@@ -2515,14 +2515,14 @@
}
},
"node_modules/@eslint/eslintrc": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz",
- "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
+ "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.5.1",
+ "espree": "^9.5.2",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -2601,9 +2601,9 @@
"dev": true
},
"node_modules/@eslint/js": {
- "version": "8.39.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz",
- "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==",
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz",
+ "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2804,9 +2804,9 @@
}
},
"node_modules/@kentcdodds/workshop-app": {
- "version": "1.35.0",
- "resolved": "https://registry.npmjs.org/@kentcdodds/workshop-app/-/workshop-app-1.35.0.tgz",
- "integrity": "sha512-TZPOgq7Sc0V7RHupLJ6Xpu5bXOxDCOLKt1Jc0NggfEER87hRBAv3gqthHK2RW3Gb0fY6GfYjF6s8SKtxSHSkYg==",
+ "version": "1.36.0",
+ "resolved": "https://registry.npmjs.org/@kentcdodds/workshop-app/-/workshop-app-1.36.0.tgz",
+ "integrity": "sha512-2ezyAeYGfQr4rUOjGtKj5SKb2u9/M5aTSHoP3OlwrZrN14YR9cnwvp/R0/Dqco2PhdI15OLaSbXSes+HsANKTA==",
"dependencies": {
"@kentcdodds/md-temp": "^3.2.1",
"@radix-ui/react-accordion": "^1.1.1",
@@ -3641,9 +3641,9 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"node_modules/@testing-library/dom": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz",
- "integrity": "sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==",
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.0.tgz",
+ "integrity": "sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
@@ -3874,9 +3874,9 @@
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"node_modules/@types/react": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz",
- "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==",
+ "version": "18.2.6",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz",
+ "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -3884,9 +3884,9 @@
}
},
"node_modules/@types/react-dom": {
- "version": "18.2.1",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.1.tgz",
- "integrity": "sha512-8QZEV9+Kwy7tXFmjJrp3XUKQSs9LTnE0KnoUb0YCguWBiNW0Yfb2iBMYZ08WPg35IR6P3Z0s00B15SwZnO26+w==",
+ "version": "18.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz",
+ "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==",
"dev": true,
"dependencies": {
"@types/react": "*"
@@ -5750,15 +5750,15 @@
}
},
"node_modules/eslint": {
- "version": "8.39.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz",
- "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==",
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz",
+ "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
- "@eslint/eslintrc": "^2.0.2",
- "@eslint/js": "8.39.0",
+ "@eslint/eslintrc": "^2.0.3",
+ "@eslint/js": "8.40.0",
"@humanwhocodes/config-array": "^0.11.8",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -5769,8 +5769,8 @@
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.0",
- "eslint-visitor-keys": "^3.4.0",
- "espree": "^9.5.1",
+ "eslint-visitor-keys": "^3.4.1",
+ "espree": "^9.5.2",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -6243,9 +6243,9 @@
}
},
"node_modules/eslint-visitor-keys": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
- "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+ "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -6412,14 +6412,14 @@
}
},
"node_modules/espree": {
- "version": "9.5.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
- "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==",
+ "version": "9.5.2",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
+ "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
"dev": true,
"dependencies": {
"acorn": "^8.8.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.0"
+ "eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -14231,14 +14231,14 @@
"dev": true
},
"@eslint/eslintrc": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz",
- "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
+ "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
- "espree": "^9.5.1",
+ "espree": "^9.5.2",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -14299,9 +14299,9 @@
}
},
"@eslint/js": {
- "version": "8.39.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz",
- "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==",
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz",
+ "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==",
"dev": true
},
"@fal-works/esbuild-plugin-global-externals": {
@@ -14469,9 +14469,9 @@
}
},
"@kentcdodds/workshop-app": {
- "version": "1.35.0",
- "resolved": "https://registry.npmjs.org/@kentcdodds/workshop-app/-/workshop-app-1.35.0.tgz",
- "integrity": "sha512-TZPOgq7Sc0V7RHupLJ6Xpu5bXOxDCOLKt1Jc0NggfEER87hRBAv3gqthHK2RW3Gb0fY6GfYjF6s8SKtxSHSkYg==",
+ "version": "1.36.0",
+ "resolved": "https://registry.npmjs.org/@kentcdodds/workshop-app/-/workshop-app-1.36.0.tgz",
+ "integrity": "sha512-2ezyAeYGfQr4rUOjGtKj5SKb2u9/M5aTSHoP3OlwrZrN14YR9cnwvp/R0/Dqco2PhdI15OLaSbXSes+HsANKTA==",
"requires": {
"@kentcdodds/md-temp": "^3.2.1",
"@radix-ui/react-accordion": "^1.1.1",
@@ -15140,9 +15140,9 @@
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"@testing-library/dom": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz",
- "integrity": "sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==",
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.0.tgz",
+ "integrity": "sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
@@ -15337,9 +15337,9 @@
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"@types/react": {
- "version": "18.2.0",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz",
- "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==",
+ "version": "18.2.6",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz",
+ "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==",
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -15347,9 +15347,9 @@
}
},
"@types/react-dom": {
- "version": "18.2.1",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.1.tgz",
- "integrity": "sha512-8QZEV9+Kwy7tXFmjJrp3XUKQSs9LTnE0KnoUb0YCguWBiNW0Yfb2iBMYZ08WPg35IR6P3Z0s00B15SwZnO26+w==",
+ "version": "18.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz",
+ "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==",
"dev": true,
"requires": {
"@types/react": "*"
@@ -16679,15 +16679,15 @@
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
},
"eslint": {
- "version": "8.39.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz",
- "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==",
+ "version": "8.40.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz",
+ "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
- "@eslint/eslintrc": "^2.0.2",
- "@eslint/js": "8.39.0",
+ "@eslint/eslintrc": "^2.0.3",
+ "@eslint/js": "8.40.0",
"@humanwhocodes/config-array": "^0.11.8",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -16698,8 +16698,8 @@
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.0",
- "eslint-visitor-keys": "^3.4.0",
- "espree": "^9.5.1",
+ "eslint-visitor-keys": "^3.4.1",
+ "espree": "^9.5.2",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -17174,20 +17174,20 @@
}
},
"eslint-visitor-keys": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
- "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+ "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
"dev": true
},
"espree": {
- "version": "9.5.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
- "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==",
+ "version": "9.5.2",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
+ "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
"dev": true,
"requires": {
"acorn": "^8.8.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^3.4.0"
+ "eslint-visitor-keys": "^3.4.1"
}
},
"esprima": {
diff --git a/package.json b/package.json
index cd6cf0b1..1d9cf943 100644
--- a/package.json
+++ b/package.json
@@ -15,19 +15,19 @@
"npm": ">=8.16.0"
},
"dependencies": {
- "@kentcdodds/workshop-app": "^1.35.0",
+ "@kentcdodds/workshop-app": "^1.36.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@playwright/test": "^1.33.0",
- "@testing-library/dom": "^9.2.0",
+ "@testing-library/dom": "^9.3.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
- "@types/react": "^18.2.0",
- "@types/react-dom": "^18.2.1",
+ "@types/react": "^18.2.6",
+ "@types/react-dom": "^18.2.4",
"cross-env": "^7.0.3",
- "eslint": "^8.39.0",
+ "eslint": "^8.40.0",
"eslint-config-react-app": "^7.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
diff --git a/playground/README.mdx b/playground/README.mdx
index cce3db9c..e5b97994 100644
--- a/playground/README.mdx
+++ b/playground/README.mdx
@@ -1 +1,97 @@
-# Compound Components
+# Latest Ref
+
+In our exercise, we have a `useDebounce` function that isn't working the way we
+want with hooks. We're going to need to "change the default" using the latest
+ref pattern.
+
+`debounce` is a pattern that's often used in user-input fields. For example, if
+you've got a signup form where the user can select their username, you probably
+want to validate for the user that the username is not taken. You want to do it
+when the user's done typing but without requiring them to do anything to trigger
+the validation. With a debounced function, you could say when the user stops
+typing for 400ms you can trigger the validation. If they start typing again
+after only 350ms then you want to start over and wait again until the user
+pauses for 400ms.
+
+In this exercise, the `debounce` function is already written. Even the
+`useDebounce` hook is implemented for you. Your job is to implement the latest
+ref pattern to fix its behavior.
+
+Our example here is a counter button that has a debounced increment function. We
+want to make it so this works:
+
+- The step is `1`
+- The user clicks the button
+- The user updates the step value to `2`
+- The user clicks the button again (before the debounce timer completes)
+- The debounce timer completes for both clicks
+- The count value should be `2` (instead of `1`)
+
+(Keep in mind, the tests are there to help you know you got it right).
+
+Before continuing here, please familiarize yourself with the exercise and how
+it's implemented... Got it? Great, let's continue.
+
+Right now, you can play around with two different problems with the way our
+exercise is implemented:
+
+```ts
+// option 1:
+// ...
+const increment = () => setCount(c => c + step)
+const debouncedIncrement = useDebounce(increment, 3000)
+// ...
+```
+
+The problem here is `useDebounce` list `increment` in the dependency list for
+`useMemo`. For this reason, any time there's a state update, we create a _new_
+debounced version of that function so the `timer` in that debounce function's
+closure is different from the previous which means we don't cancel that timeout.
+Ultimate this is the bug our users experience:
+
+- The user clicks the button
+- The user updates the step value
+- The user clicks the button again
+- The first debounce timer completes
+- The count value is incremented by the step value at the time the first click
+ happened
+- The second debounce timer completes
+- The count value is incremented by the step value at the time the second click
+ happened
+
+This is not what we want at all! And the reason it's a problem is because we're
+not memoizing the callback that's going into our `useMemo` dependency list.
+
+So the alternative solution is we could change our `useDebounce` API to require
+you pass a memoized callback:
+
+```ts
+// option 2:
+// ...
+const increment = React.useCallback(() => setCount(c => c + step), [step])
+const debouncedIncrement = useDebounce(increment, 3000)
+// ...
+```
+
+But again, this callback function will be updated when the `step` value changes
+which means we'll get another instance of the `debouncedIncrement`. Dah! So the
+user experience doesn't actually change with this adjustment _and_ we have a
+less fun API. The latest ref pattern will give us a nice API and we'll avoid
+this problem.
+
+I've made the debounce value last `3000ms` to make it easier for you to observe
+and test the behavior, but you can feel free to adjust that as you like. The
+tests can also help you make sure you've got things working well.
+
+Files
+
+
+
+