diff --git a/.all-contributorsrc b/.all-contributorsrc deleted file mode 100644 index af229275..00000000 --- a/.all-contributorsrc +++ /dev/null @@ -1,413 +0,0 @@ -{ - "projectName": "advanced-react-patterns", - "projectOwner": "kentcdodds", - "repoType": "github", - "files": [ - "README.md" - ], - "imageSize": 100, - "commit": false, - "contributors": [ - { - "login": "kentcdodds", - "name": "Kent C. Dodds", - "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", - "profile": "https://kentcdodds.com", - "contributions": [ - "code", - "doc", - "infra", - "test" - ] - }, - { - "login": "FWeinb", - "name": "FWeinb", - "avatar_url": "https://avatars0.githubusercontent.com/u/1250430?v=4", - "profile": "https://github.com/FWeinb", - "contributions": [ - "bug", - "ideas" - ] - }, - { - "login": "dlannoye", - "name": "David Lannoye", - "avatar_url": "https://avatars2.githubusercontent.com/u/1383720?v=4", - "profile": "https://github.com/dlannoye", - "contributions": [ - "bug", - "doc" - ] - }, - { - "login": "colinrcummings", - "name": "Colin Cummings", - "avatar_url": "https://avatars2.githubusercontent.com/u/9815009?s=460&v=4", - "profile": "https://github.com/colinrcummings", - "contributions": [ - "code", - "test" - ] - }, - { - "login": "bkoltai", - "name": "Benji Koltai", - "avatar_url": "https://avatars2.githubusercontent.com/u/464764?v=4", - "profile": "https://github.com/bkoltai", - "contributions": [ - "doc" - ] - }, - { - "login": "baggasumit", - "name": "Sumit Bagga", - "avatar_url": "https://avatars1.githubusercontent.com/u/1779959?v=4", - "profile": "http://baggasumit.github.io", - "contributions": [ - "doc" - ] - }, - { - "login": "Tarabyte", - "name": "Yury Tarabanko", - "avatar_url": "https://avatars0.githubusercontent.com/u/2027010?v=4", - "profile": "https://github.com/Tarabyte", - "contributions": [ - "code" - ] - }, - { - "login": "themostcolm", - "name": "Alex Wendte", - "avatar_url": "https://avatars2.githubusercontent.com/u/5779538?v=4", - "profile": "http://www.wendtedesigns.com/", - "contributions": [ - "code" - ] - }, - { - "login": "CompuIves", - "name": "Ives van Hoorne", - "avatar_url": "https://avatars3.githubusercontent.com/u/587016?v=4", - "profile": "https://twitter.com/CompuIves", - "contributions": [ - "code", - "test" - ] - }, - { - "login": "lgandecki", - "name": "Łukasz Gandecki", - "avatar_url": "https://avatars1.githubusercontent.com/u/4002543?v=4", - "profile": "http://team.thebrain.pro", - "contributions": [ - "doc" - ] - }, - { - "login": "deniztetik", - "name": "Deniz Tetik", - "avatar_url": "https://avatars0.githubusercontent.com/u/14167019?v=4", - "profile": "https://github.com/deniztetik", - "contributions": [ - "content" - ] - }, - { - "login": "Ruffeng", - "name": "Ruffeng", - "avatar_url": "https://avatars1.githubusercontent.com/u/18511772?v=4", - "profile": "https://github.com/Ruffeng", - "contributions": [ - "content", - "code" - ] - }, - { - "login": "jdorfman", - "name": "Justin Dorfman", - "avatar_url": "https://avatars1.githubusercontent.com/u/398230?v=4", - "profile": "https://stackshare.io/jdorfman/decisions", - "contributions": [ - "fundingFinding" - ] - }, - { - "login": "AlexMunoz", - "name": "Alex Munoz", - "avatar_url": "https://avatars3.githubusercontent.com/u/3093946?v=4", - "profile": "http://alexmunoz.github.io", - "contributions": [ - "doc" - ] - }, - { - "login": "marcosvega91", - "name": "Marco Moretti", - "avatar_url": "https://avatars2.githubusercontent.com/u/5365582?v=4", - "profile": "https://github.com/marcosvega91", - "contributions": [ - "code" - ] - }, - { - "login": "emipc", - "name": "Emili", - "avatar_url": "https://avatars1.githubusercontent.com/u/26004903?v=4", - "profile": "https://github.com/emipc", - "contributions": [ - "doc" - ] - }, - { - "login": "balavishnuvj", - "name": "balavishnuvj", - "avatar_url": "https://avatars3.githubusercontent.com/u/13718688?v=4", - "profile": "https://github.com/balavishnuvj", - "contributions": [ - "code" - ] - }, - { - "login": "PritamSangani", - "name": "Pritam Sangani", - "avatar_url": "https://avatars3.githubusercontent.com/u/22857896?v=4", - "profile": "https://www.linkedin.com/in/pritamsangani/", - "contributions": [ - "code" - ] - }, - { - "login": "kocvrek", - "name": "Kasia Kosturek", - "avatar_url": "https://avatars3.githubusercontent.com/u/36547835?v=4", - "profile": "http://linkedin.com/in/katarzynakosturek/", - "contributions": [ - "doc" - ] - }, - { - "login": "emzoumpo", - "name": "Emmanouil Zoumpoulakis", - "avatar_url": "https://avatars2.githubusercontent.com/u/2103443?v=4", - "profile": "https://github.com/emzoumpo", - "contributions": [ - "doc" - ] - }, - { - "login": "Aprillion", - "name": "Peter HozΓ‘k", - "avatar_url": "https://avatars0.githubusercontent.com/u/1087670?v=4", - "profile": "http://peter.hozak.info/", - "contributions": [ - "code" - ] - }, - { - "login": "nawok", - "name": "Pavel Fomchenkov", - "avatar_url": "https://avatars3.githubusercontent.com/u/159773?v=4", - "profile": "https://github.com/nawok", - "contributions": [ - "doc" - ] - }, - { - "login": "seemaullal", - "name": "Seema Ullal", - "avatar_url": "https://avatars0.githubusercontent.com/u/8728285?v=4", - "profile": "http://www.seemaullal.com", - "contributions": [ - "doc" - ] - }, - { - "login": "patrickclery", - "name": "Patrick Clery", - "avatar_url": "https://avatars0.githubusercontent.com/u/25733135?v=4", - "profile": "https://git.io/JfYj5", - "contributions": [ - "doc" - ] - }, - { - "login": "degeens", - "name": "Stijn Geens", - "avatar_url": "https://avatars2.githubusercontent.com/u/33414262?v=4", - "profile": "https://github.com/degeens", - "contributions": [ - "doc" - ] - }, - { - "login": "MichaelDeBoey", - "name": "MichaΓ«l De Boey", - "avatar_url": "https://avatars3.githubusercontent.com/u/6643991?v=4", - "profile": "https://michaeldeboey.be", - "contributions": [ - "code" - ] - }, - { - "login": "DaleSeo", - "name": "Dale Seo", - "avatar_url": "https://avatars1.githubusercontent.com/u/5466341?v=4", - "profile": "https://www.daleseo.com", - "contributions": [ - "doc" - ] - }, - { - "login": "bobbywarner", - "name": "Bobby Warner", - "avatar_url": "https://avatars0.githubusercontent.com/u/554961?v=4", - "profile": "http://bobbywarner.com", - "contributions": [ - "code" - ] - }, - { - "login": "sophiabrandt", - "name": "Sophia Brandt", - "avatar_url": "https://avatars0.githubusercontent.com/u/16630701?v=4", - "profile": "https://www.sophiabrandt.com", - "contributions": [ - "doc" - ] - }, - { - "login": "ph08n1x", - "name": "ph08n1x", - "avatar_url": "https://avatars.githubusercontent.com/u/4249732?v=4", - "profile": "https://github.com/ph08n1x", - "contributions": [ - "doc" - ] - }, - { - "login": "Suhas010", - "name": "Suhas R More", - "avatar_url": "https://avatars.githubusercontent.com/u/8597576?v=4", - "profile": "https://www.suhas010.com", - "contributions": [ - "code" - ] - }, - { - "login": "0xnoob", - "name": "0xnoob", - "avatar_url": "https://avatars.githubusercontent.com/u/49793844?v=4", - "profile": "https://github.com/0xnoob", - "contributions": [ - "code" - ] - }, - { - "login": "pvinis", - "name": "Pavlos Vinieratos", - "avatar_url": "https://avatars.githubusercontent.com/u/100233?v=4", - "profile": "http://pavlos.dev", - "contributions": [ - "doc" - ] - }, - { - "login": "infoxicator", - "name": "Ruben Casas", - "avatar_url": "https://avatars.githubusercontent.com/u/17012976?v=4", - "profile": "https://github.com/infoxicator", - "contributions": [ - "code", - "doc" - ] - }, - { - "login": "marioleed", - "name": "Mario Sannum", - "avatar_url": "https://avatars.githubusercontent.com/u/1763448?v=4", - "profile": "https://github.com/marioleed", - "contributions": [ - "code" - ] - }, - { - "login": "AbePlays", - "name": "Abhishek Rawat", - "avatar_url": "https://avatars.githubusercontent.com/u/47311875?v=4", - "profile": "https://wrongabhishek.com", - "contributions": [ - "doc" - ] - }, - { - "login": "Fullchee", - "name": "Fullchee Zhang", - "avatar_url": "https://avatars.githubusercontent.com/u/11246258?v=4", - "profile": "http://fullchee.com", - "contributions": [ - "doc" - ] - }, - { - "login": "YaseenAlGailani", - "name": "Yaseen", - "avatar_url": "https://avatars.githubusercontent.com/u/23329119?v=4", - "profile": "https://github.com/YaseenAlGailani", - "contributions": [ - "doc" - ] - }, - { - "login": "jpotts18", - "name": "Jeff Potter", - "avatar_url": "https://avatars.githubusercontent.com/u/2466729?v=4", - "profile": "http://mangovoice.com", - "contributions": [ - "doc" - ] - }, - { - "login": "kenneth-gray", - "name": "Kenny Gray", - "avatar_url": "https://avatars.githubusercontent.com/u/10341832?v=4", - "profile": "https://github.com/kenneth-gray", - "contributions": [ - "bug" - ] - }, - { - "login": "jorgeruvalcaba", - "name": "Jorge Ruvalcaba", - "avatar_url": "https://avatars.githubusercontent.com/u/7416811?v=4", - "profile": "http://jorgearuv.dev", - "contributions": [ - "doc" - ] - }, - { - "login": "creador-dev", - "name": "Pawan Kumar", - "avatar_url": "https://avatars.githubusercontent.com/u/40248406?v=4", - "profile": "https://creador.dev", - "contributions": [ - "doc" - ] - }, - { - "login": "junagao", - "name": "juliane nagao", - "avatar_url": "https://avatars.githubusercontent.com/u/615616?v=4", - "profile": "http://junagao.com", - "contributions": [ - "doc" - ] - } - ], - "repoHost": "https://github.com", - "contributorsPerLine": 7, - "skipCi": true, - "commitConvention": "angular", - "commitType": "docs" -} diff --git a/.eslintignore b/.eslintignore index 674017c0..f8d83c97 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,8 +1,7 @@ -.DS_Store node_modules -coverage build -.idea/ -.vscode/ -.eslintcache -/playwright-report +public/build +playwright-report +test-results +server-build +scripts diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000..3663b158 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,38 @@ +/** @type {import('@types/eslint').Linter.BaseConfig} */ +module.exports = { + extends: [ + '@remix-run/eslint-config', + '@remix-run/eslint-config/node', + 'prettier', + ], + rules: { + // playwright requires destructuring in fixtures even if you don't use anything πŸ€·β€β™‚οΈ + 'no-empty-pattern': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + 'no-unused-expressions': 'off', + '@typescript-eslint/consistent-type-imports': [ + 'warn', + { + prefer: 'type-imports', + disallowTypeAnnotations: true, + fixStyle: 'inline-type-imports', + }, + ], + 'import/no-duplicates': ['warn', { 'prefer-inline': true }], + 'import/consistent-type-specifier-style': ['warn', 'prefer-inline'], + 'import/order': [ + 'warn', + { + alphabetize: { order: 'asc', caseInsensitive: true }, + groups: [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index', + ], + }, + ], + }, +} diff --git a/.gitignore b/.gitignore index 9eaee462..b338bcfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ -.DS_Store node_modules -coverage -build -.idea/ -.vscode/ -.eslintcache -/playwright-report -/test-results + +workspace/ +**/.cache/ +**/build/ +**/public/build +**/playwright-report +data.db /playground +**/tsconfig.tsbuildinfo + +# in a real app you'd want to not commit the .env +# file as well, but since this is for a workshop +# we're going to keep them around. +# .env diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 04ad16e1..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,104 +0,0 @@ -## Learn more about this file at 'https://www.gitpod.io/docs/references/gitpod-yml' -## -## This '.gitpod.yml' file when placed at the root of a project instructs -## Gitpod how to prepare & build the project, start development environments -## and configure continuous prebuilds. Prebuilds when enabled builds a project -## like a CI server so you can start coding right away - no more waiting for -## dependencies to download and builds to finish when reviewing pull-requests -## or hacking on something new. -## -## With Gitpod you can develop software from any device (even iPads) via -## desktop or browser based versions of VS Code or any JetBrains IDE and -## customise it to your individual needs - from themes to extensions, you -## have full control. -## -## The easiest way to try out Gitpod is install the browser extenion: -## 'https://www.gitpod.io/docs/browser-extension' or by prefixing -## 'https://gitpod.io#' to the source control URL of any project. -## -## For example: 'https://gitpod.io#https://github.com/gitpod-io/gitpod' - - -## The 'tasks' section defines how Gitpod prepares & builds this project -## and how it can start development servers. With Gitpod, there are three -## types of tasks: -## -## - before: Use this for tasks that need to run before init and before command. -## - init: Use this to configure prebuilds of heavy-lifting tasks such as -## downloading dependencies or compiling source code. -## - command: Use this to start your database or application when the workspace starts. -## -## Learn more about these tasks at 'https://www.gitpod.io/docs/config-start-tasks' - -tasks: - - name: App - init: npm install - command: npm run start - openMode: split-left - - - name: Test - command: npm run test - openMode: split-right - - - name: Set up email - command: | - clear - printf "\n\n\n" - printf "\u001b[36;1mAutofilling Email\u001b[0m\n" - printf "\u001b[2;1mEach exercise comes with a elaboration form to help your retention. Providing your email now will mean you don't have to provide it each time you fill out the form.\u001b[0m\n" - npx "https://gist.github.com/kentcdodds/2d44448a8997b9964b1be44cd294d1f5" \ - && exit 0 -## The 'ports' section defines various ports your may listen on are -## configured in Gitpod on an authenticated URL. By default, all ports -## are in private visibility state. -## -## Learn more about ports at 'https://www.gitpod.io/docs/config-ports' - -ports: - - port: 3000 # alternatively configure entire ranges via '8080-8090' - visibility: private # either 'public' or 'private' (default) - onOpen: open-browser # either 'open-browser', 'open-preview' or 'ignore' - -## The 'vscode' section defines a list of Visual Studio Code extensions from -## the OpenVSX.org registry to be installed upon workspace startup. OpenVSX -## is an open alternative to the proprietary Visual Studio Code Marketplace -## and extensions can be added by sending a pull-request with the extension -## identifier to https://github.com/open-vsx/publish-extensions -## -## The identifier of an extension is always ${publisher}.${name}. -## -## For example: 'vscodevim.vim' -## -## Learn more at 'https://www.gitpod.io/docs/ides-and-editors/vscode' - -vscode: - extensions: - - VisualStudioExptTeam.vscodeintellicode - - dbaeumer.vscode-eslint - - formulahendry.auto-rename-tag - - esbenp.prettier-vscode - - ms-azuretools.vscode-docker - -## The 'github' section defines configuration of continuous prebuilds -## for GitHub repositories when the GitHub application -## 'https://github.com/apps/gitpod-io' is installed in GitHub and granted -## permissions to access the repository. -## -## Learn more at 'https://www.gitpod.io/docs/prebuilds' - -github: - prebuilds: - # enable for the default branch - master: true - # enable for all branches in this repo - branches: false - # enable for pull requests coming from this repo - pullRequests: false - # enable for pull requests coming from forks - pullRequestsFromForks: false - # add a check to pull requests - addCheck: false - # add a "Review in Gitpod" button as a comment to pull requests - addComment: false - # add a "Review in Gitpod" button to the pull request's description - addBadge: false diff --git a/.npmrc b/.npmrc index 5984d095..668efa17 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,2 @@ -registry=https://registry.npmjs.org/ -package-lock=true -yes=true legacy-peer-deps=true +registry=https://registry.npmjs.org/ diff --git a/.prettierignore b/.prettierignore index ba1e353b..c8d863d1 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,9 +1,11 @@ -.DS_Store node_modules -coverage -build -.idea/ -.vscode/ -.eslintcache -/playwright-report -package.json + +**/build/** +**/public/build/** +.env + +**/package.json +**/tsconfig.json + +**/package-lock.json +**/playwright-report/** diff --git a/.prettierrc b/.prettierrc index db4ac60e..da3a81a9 100644 --- a/.prettierrc +++ b/.prettierrc @@ -19,12 +19,10 @@ "useTabs": true, "overrides": [ { - "files": [ - "**/*.json" - ], + "files": ["**/*.json"], "options": { "useTabs": false } } ] -} \ No newline at end of file +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c0e8d3cf..946f9442 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ { - "recommendations": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "formulahendry.auto-rename-tag", - "VisualStudioExptTeam.vscodeintellicode" - ] + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "formulahendry.auto-rename-tag", + "VisualStudioExptTeam.vscodeintellicode" + ] } diff --git a/.vscode/settings.kcd.json b/.vscode/settings.kcd.json index ae713367..1e7850a0 100644 --- a/.vscode/settings.kcd.json +++ b/.vscode/settings.kcd.json @@ -1,66 +1,66 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.detectIndentation": true, - "editor.fontFamily": "'Dank Mono', Menlo, Monaco, 'Courier New', monospace", - "editor.fontLigatures": false, - "editor.rulers": [80], - "editor.snippetSuggestions": "top", - "editor.wordBasedSuggestions": false, - "editor.suggest.localityBonus": true, - "editor.acceptSuggestionOnCommitCharacter": false, - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.suggestSelection": "recentlyUsed", - "editor.suggest.showKeywords": false - }, - "editor.renderWhitespace": "boundary", - "files.defaultLanguage": "{activeEditorLanguage}", - "javascript.validate.enable": false, - "search.exclude": { - "**/node_modules": true, - "**/bower_components": true, - "**/coverage": true, - "**/dist": true, - "**/build": true, - "**/.build": true, - "**/.gh-pages": true - }, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": false - }, - "eslint.validate": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], - "eslint.options": { - "env": { - "browser": true, - "jest/globals": true, - "es6": true - }, - "parserOptions": { - "ecmaVersion": 2019, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true - } - }, - "rules": { - "no-debugger": "off" - } - }, - "workbench.colorTheme": "Night Owl", - "workbench.iconTheme": "material-icon-theme", - "breadcrumbs.enabled": true, - "grunt.autoDetect": "off", - "gulp.autoDetect": "off", - "npm.runSilent": true, - "explorer.confirmDragAndDrop": false, - "editor.formatOnPaste": false, - "editor.cursorSmoothCaretAnimation": true, - "editor.smoothScrolling": true, - "php.suggest.basic": false + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.detectIndentation": true, + "editor.fontFamily": "'Dank Mono', Menlo, Monaco, 'Courier New', monospace", + "editor.fontLigatures": false, + "editor.rulers": [80], + "editor.snippetSuggestions": "top", + "editor.wordBasedSuggestions": false, + "editor.suggest.localityBonus": true, + "editor.acceptSuggestionOnCommitCharacter": false, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.suggestSelection": "recentlyUsed", + "editor.suggest.showKeywords": false + }, + "editor.renderWhitespace": "boundary", + "files.defaultLanguage": "{activeEditorLanguage}", + "javascript.validate.enable": false, + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/coverage": true, + "**/dist": true, + "**/build": true, + "**/.build": true, + "**/.gh-pages": true + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": false + }, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "eslint.options": { + "env": { + "browser": true, + "jest/globals": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2019, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + "no-debugger": "off" + } + }, + "workbench.colorTheme": "Night Owl", + "workbench.iconTheme": "material-icon-theme", + "breadcrumbs.enabled": true, + "grunt.autoDetect": "off", + "gulp.autoDetect": "off", + "npm.runSilent": true, + "explorer.confirmDragAndDrop": false, + "editor.formatOnPaste": false, + "editor.cursorSmoothCaretAnimation": true, + "editor.smoothScrolling": true, + "php.suggest.basic": false } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index e0d1c00b..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1 +0,0 @@ -Please refer to [kentcdodds.com/conduct/](https://kentcdodds.com/conduct/) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 3a649275..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,38 +0,0 @@ -# Contributing - -Thanks for being willing to contribute! - -**Working on your first Pull Request?** You can learn how from this _free_ -series [How to Contribute to an Open Source Project on GitHub][egghead] - -## Project setup - -1. Fork and clone the repo -2. Run `npm run setup -s` to install dependencies and run validation -3. Create a branch for your PR with `git checkout -b pr/your-branch-name` - -> Tip: Keep your `main` branch pointing at the original repository and make pull -> requests from branches on your fork. To do this, run: -> -> ``` -> git remote add upstream https://github.com/kentcdodds/advanced-react-patterns.git -> git fetch upstream -> git branch --set-upstream-to=upstream/main main -> ``` -> -> This will add the original repository as a "remote" called "upstream," Then -> fetch the git information from that remote, then set your local `main` branch -> to use the upstream main branch whenever you run `git pull`. Then you can make -> all of your pull request branches based on this `main` branch. Whenever you -> want to update your version of `main`, do a regular `git pull`. - -## Help needed - -Please checkout the [the open issues][issues] - -Also, please watch the repo and respond to questions/bug reports/feature -requests! Thanks! - -[egghead]: - https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github -[issues]: https://github.com/kentcdodds/advanced-react-patterns/issues diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 21656d2b..00000000 --- a/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM node:16 - -WORKDIR /app -COPY . . -RUN NO_EMAIL_AUTOFILL=true node setup - -CMD ["npm", "start"] diff --git a/LICENSE.md b/LICENSE.md index 04e0cb13..ee60ae28 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ This material is available for private, non-commercial use under the [GPL version 3](http://www.gnu.org/licenses/gpl-3.0-standalone.html). If you -would like to use this material to conduct your own workshop, please contact me -at me@kentcdodds.com +would like to use this material to conduct your own workshop, please contact us +at team@epicweb.dev diff --git a/README.md b/README.md index 8fd66cc5..c758d7a2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
-

🀯 Advanced React Patterns πŸš€ EpicReact.Dev

+

🀯 Advanced React Patterns

Learn how to build simple and flexible React Components and Hooks using modern patterns @@ -9,11 +9,18 @@ weaknesses of each, so you know which to reach for to provide your custom hooks and components the flexibility and power you need.

+
+ +
- +
+ Learn React from Start to Finish
@@ -22,10 +29,8 @@ [![Build Status][build-badge]][build] -[![All Contributors][all-contributors-badge]](#contributors) [![GPL 3.0 License][license-badge]][license] [![Code of Conduct][coc-badge]][coc] -[![Gitpod ready-to-code][gitpod-badge]](https://gitpod.io/#https://github.com/kentcdodds/advanced-react-patterns) ## Prerequisites @@ -37,28 +42,18 @@ - The more experience you have with building React abstractions, the more helpful this workshop will be for you. -> NOTE: The EpicReact.dev videos were recorded with React version ^16.13 and all -> material in this repo has been updated to React version ^18. Differences are -> minor and any relevant differences are noted in the instructions. - -## Quick start - -It's recommended you run everything in the same environment you work in every -day, but if you don't want to set up the repository locally, you can get started -in one click with [Gitpod](https://gitpod.io), -[CodeSandbox](https://codesandbox.io/s/github/kentcdodds/advanced-react-patterns), -or by following the [video demo](https://www.youtube.com/watch?v=gCoVJm3hGk4) -instructions for [GitHub Codespaces](https://github.com/features/codespaces). +## Pre-workshop Resources -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/kentcdodds/advanced-react-patterns) +Here are some resources you can read before taking the workshop to get you up to +speed on some of the tools and concepts we'll be covering: -For a local development environment, follow the instructions below +- TODO: add resources ## System Requirements -- [git][git] v2.13 or greater -- [NodeJS][node] `14 || 16 || 18 || 19` -- [npm][npm] v8.16.0 or greater +- [git][git] v2.18 or greater +- [NodeJS][node] v18 or greater +- [npm][npm] v8 or greater All of these must be available in your `PATH`. To verify things are set up properly, you can run this: @@ -75,195 +70,39 @@ variable and how to fix it here for [windows][win-path] or ## Setup -> If you want to commit and push your work as you go, you'll want to -> [fork](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo) -> first and then clone your fork rather than this repo directly. +This is a pretty large project (it's actually many apps in one) so it can take +several minutes to get everything set up the first time. Please have a strong +network connection before running the setup and grab a snack. -After you've made sure to have the correct things (and versions) installed, you -should be able to just run a few commands to get set up: +Follow these steps to get this set up: -```shell -git clone https://github.com/kentcdodds/advanced-react-patterns.git +```sh nonumber +git clone --depth 1 https://github.com/epicweb-dev/advanced-react-patterns.git cd advanced-react-patterns -git checkout next2 -node setup -``` - -This may take a few minutes. - -If you get any errors, please read through them and see if you can find out what -the problem is. If you can't work it out on your own then please [file an -issue][issue] and provide _all_ the output from the commands you ran (even if -it's a lot). - -If you can't get the setup script to work, then just make sure you have the -right versions of the requirements listed above, and run the following commands: - -```shell -npm install -npm run validate -``` - -If you are still unable to fix issues and you know how to use Docker 🐳 you can -setup the project with the following command: - -```shell -docker-compose up -``` - -## Running the app - -To get the app up and running (and really see if it worked), run: - -```shell -npm start +npm run setup ``` -Now open your browser to the address that's logged out for you and you're good -to get started! - -## Running the tests - -The test script in the `package.json` runs the tests on the solutions (these -should all pass). To run the tests against your own work, you simply open the -problem page and click the "Test" tab. - -## TypeScript - -You can go through this workshop with TypeScript or JavaScript. All the files -are TypeScript files, but if you run the script in `./scripts/remove-ts` then -all the files will be renamed and all the TypeScript typing code will be -removed. I do not recommend doing this, but if you want to, you can. - -### Exercises - -- `exercises/*.*/README.md`: Exercise background information -- `exercises/*.*/*.problem.*/README.*.md`: Problem Instructions -- `exercises/*.*/*.problem.*/*.tsx`: Exercise with Emoji helpers πŸ‘ˆ You spend - most of your time here. -- `exercises/*.*/*.solution.*/*.tsx`: Solved version +If you experience errors here, please open [an issue][issue] with as many +details as you can offer. -The purpose of the exercise is **not** for you to work through all the material. -It's intended to get your brain thinking about the right questions to ask me as -_I_ walk through the material. - -### Helpful Emoji 🐨 🦺 πŸ’° πŸ“ πŸ¦‰ πŸ“œ πŸ’£ πŸ’ͺ 🏁 πŸ‘¨β€πŸ’Ό 🚨 - -Each exercise has comments in it to help you get through the exercise. These fun -emoji characters are here to help you. - -- **Kody the Koala** 🐨 will tell you when there's something specific you should - do -- **Lily the Life Jacket** 🦺 will help you with any TypeScript-specific parts - of the exercises -- **Marty the Money Bag** πŸ’° will give you specific tips (and sometimes code) - along the way -- **Nancy the Notepad** πŸ“ will encourage you to take notes on what you're - learning -- **Olivia the Owl** πŸ¦‰ will give you useful tidbits/best practice notes -- **Dominic the Document** πŸ“œ will give you links to useful documentation -- **Berry the Bomb** πŸ’£ will be hanging around anywhere you need to blow stuff - up (delete code) -- **Matthew the Muscle** πŸ’ͺ will indicate that you're working with an exercise -- **Chuck the Checkered Flag** 🏁 will indicate that you're working with a final -- **Peter the Product Manager** πŸ‘¨β€πŸ’Ό helps us know what our users want -- **Alfred the Alert** 🚨 will occasionally show up in the test failures with - potential explanations for why the tests are failing - -## Contributors - -Thanks goes to these wonderful people -([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Kent C. Dodds
Kent C. Dodds

πŸ’» πŸ“– πŸš‡ ⚠️
FWeinb
FWeinb

πŸ› πŸ€”
David Lannoye
David Lannoye

πŸ› πŸ“–
Colin Cummings
Colin Cummings

πŸ’» ⚠️
Benji Koltai
Benji Koltai

πŸ“–
Sumit Bagga
Sumit Bagga

πŸ“–
Yury Tarabanko
Yury Tarabanko

πŸ’»
Alex Wendte
Alex Wendte

πŸ’»
Ives van Hoorne
Ives van Hoorne

πŸ’» ⚠️
Łukasz Gandecki
Łukasz Gandecki

πŸ“–
Deniz Tetik
Deniz Tetik

πŸ–‹
Ruffeng
Ruffeng

πŸ–‹ πŸ’»
Justin Dorfman
Justin Dorfman

πŸ”
Alex Munoz
Alex Munoz

πŸ“–
Marco Moretti
Marco Moretti

πŸ’»
Emili
Emili

πŸ“–
balavishnuvj
balavishnuvj

πŸ’»
Pritam Sangani
Pritam Sangani

πŸ’»
Kasia Kosturek
Kasia Kosturek

πŸ“–
Emmanouil Zoumpoulakis
Emmanouil Zoumpoulakis

πŸ“–
Peter HozΓ‘k
Peter HozΓ‘k

πŸ’»
Pavel Fomchenkov
Pavel Fomchenkov

πŸ“–
Seema Ullal
Seema Ullal

πŸ“–
Patrick Clery
Patrick Clery

πŸ“–
Stijn Geens
Stijn Geens

πŸ“–
MichaΓ«l De Boey
MichaΓ«l De Boey

πŸ’»
Dale Seo
Dale Seo

πŸ“–
Bobby Warner
Bobby Warner

πŸ’»
Sophia Brandt
Sophia Brandt

πŸ“–
ph08n1x
ph08n1x

πŸ“–
Suhas R More
Suhas R More

πŸ’»
0xnoob
0xnoob

πŸ’»
Pavlos Vinieratos
Pavlos Vinieratos

πŸ“–
Ruben Casas
Ruben Casas

πŸ’» πŸ“–
Mario Sannum
Mario Sannum

πŸ’»
Abhishek Rawat
Abhishek Rawat

πŸ“–
Fullchee Zhang
Fullchee Zhang

πŸ“–
Yaseen
Yaseen

πŸ“–
Jeff Potter
Jeff Potter

πŸ“–
Kenny Gray
Kenny Gray

πŸ›
Jorge Ruvalcaba
Jorge Ruvalcaba

πŸ“–
Pawan Kumar
Pawan Kumar

πŸ“–
juliane nagao
juliane nagao

πŸ“–
- - - +## The Workshop App - +Learn all about the workshop app on the +[Epic Web Getting Started Guide](https://www.epicweb.dev/get-started). -This project follows the -[all-contributors](https://github.com/kentcdodds/all-contributors) -specification. Contributions of any kind welcome! +[![Kent with the workshop app in the background](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/280407082-0e012138-e01d-45d5-abf2-86ffe5d03c69.png)](https://www.epicweb.dev/get-started) [npm]: https://www.npmjs.com/ [node]: https://nodejs.org [git]: https://git-scm.com/ -[build-badge]: https://img.shields.io/github/actions/workflow/status/kentcdodds/advanced-react-patterns/validate.yml?branch=next2&style=flat-square&logo=github -[build]: https://github.com/kentcdodds/advanced-react-patterns/actions?query=workflow%3Avalidate +[build-badge]: https://img.shields.io/github/actions/workflow/status/epicweb-dev/advanced-react-patterns/validate.yml?branch=main&logo=github&style=flat-square +[build]: https://github.com/epicweb-dev/advanced-react-patterns/actions?query=workflow%3Avalidate [license-badge]: https://img.shields.io/badge/license-GPL%203.0%20License-blue.svg?style=flat-square -[license]: https://github.com/kentcdodds/advanced-react-patterns/blob/main/LICENSE.md +[license]: https://github.com/epicweb-dev/advanced-react-patterns/blob/main/LICENSE [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square -[gitpod-badge]: https://img.shields.io/badge/Gitpod-ready--to--code-908a85?logo=gitpod -[coc]: https://github.com/kentcdodds/advanced-react-patterns/blob/main/CODE_OF_CONDUCT.md -[emojis]: https://github.com/kentcdodds/all-contributors#emoji-key -[all-contributors]: https://github.com/kentcdodds/all-contributors -[all-contributors-badge]: https://img.shields.io/github/all-contributors/kentcdodds/advanced-react-patterns?color=orange&style=flat-square +[coc]: https://kentcdodds.com/conduct [win-path]: https://www.howtogeek.com/118594/how-to-edit-your-system-path-for-easy-command-line-access/ [mac-path]: http://stackoverflow.com/a/24322978/971592 -[issue]: https://github.com/kentcdodds/advanced-react-patterns/issues/new +[issue]: https://github.com/epicweb-dev/advanced-react-patterns/issues/new diff --git a/craco.config.js b/craco.config.js deleted file mode 100644 index 1e27ee64..00000000 --- a/craco.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@kentcdodds/react-workshop-app/craco.config') diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 0cb30eb7..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3' - -services: - node: - build: . - volumes: - - ./src:/app/src - ports: - - '3000:3000' diff --git a/examples/resetting-via-key-prop/README.md b/examples/resetting-via-key-prop/README.md deleted file mode 100644 index 82257a62..00000000 --- a/examples/resetting-via-key-prop/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Resetting via Key Prop - -The key prop is a special prop that can be used to reset the state of a -component. When the key prop changes, the component is unmounted and remounted. -This can be a useful way to reset the state of a component in some cases. - -In this case, by changing the key prop, we successfully reset the state, but we -also lose the animation because we're not just changing the state of the -component, we're also creating a brand new DOM element to represent the switch. -So probably better to use the state initializer pattern. diff --git a/examples/resetting-via-key-prop/index.tsx b/examples/resetting-via-key-prop/index.tsx deleted file mode 100644 index 7cbce8b4..00000000 --- a/examples/resetting-via-key-prop/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// this is basically the same as the state initializer except the AppResetter -// component at the bottom. -// http://localhost:3000/app/examples.resetting-via-key-prop - -import * as React from 'react' -import * as ReactDOM from 'react-dom/client' -import { Switch } from '~/shared/switch.tsx' - -function callAll>( - ...fns: Array<((...args: Args) => unknown) | undefined> -) { - return (...args: Args) => fns.forEach(fn => fn?.(...args)) -} - -type ToggleState = { on: boolean } -type ToggleAction = { type: 'toggle' } - -function toggleReducer(state: ToggleState, action: ToggleAction) { - switch (action.type) { - case 'toggle': { - return { on: !state.on } - } - } -} - -function useToggle({ initialOn = false } = {}) { - const [state, dispatch] = React.useReducer(toggleReducer, { on: initialOn }) - const { on } = state - - const toggle = () => dispatch({ type: 'toggle' }) - - function getTogglerProps({ - onClick, - ...props - }: { onClick?: React.DOMAttributes['onClick'] } & Props) { - return { - 'aria-checked': on, - onClick: callAll(onClick, toggle), - ...props, - } - } - - return { - on, - toggle, - getTogglerProps, - } -} - -function App({ onReset }: { onReset: () => void }) { - const { on, getTogglerProps } = useToggle({ initialOn: true }) - - return ( -
- - -
- ) -} - -function AppResetter() { - const [key, setKey] = React.useState(0) - const handleReset = () => setKey(k => k + 1) - return -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() diff --git a/examples/warnings/README.md b/examples/warnings/README.md deleted file mode 100644 index 87cebe97..00000000 --- a/examples/warnings/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Controlled Input Warnings - -When you run this example, you'll find that there are three unique warnings you -get in the console. Play around with the example and see if you can figure out -what the warnings are and think of what problems they're protecting you from. diff --git a/examples/warnings/index.tsx b/examples/warnings/index.tsx deleted file mode 100644 index 2e7201c6..00000000 --- a/examples/warnings/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// http://localhost:3000/app/examples.warnings - -import * as React from 'react' -import * as ReactDOM from 'react-dom/client' - -function App() { - const [name, setName] = React.useState() - const [animal, setAnimal] = React.useState('tiger') - return ( -
-
- -
-
- - -
-
- - -
-
- ) -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() diff --git a/exercises/01.latest-ref/01.problem/README.mdx b/exercises/01.latest-ref/01.problem/README.mdx index aefb359e..e1297c8d 100644 --- a/exercises/01.latest-ref/01.problem/README.mdx +++ b/exercises/01.latest-ref/01.problem/README.mdx @@ -68,7 +68,7 @@ you pass a memoized callback: ```ts // option 2: // ... -const increment = React.useCallback(() => setCount(c => c + step), [step]) +const increment = useMemoCallback(() => setCount(c => c + step), [step]) const debouncedIncrement = useDebounce(increment, 3000) // ... ``` diff --git a/exercises/01.latest-ref/01.problem/index.tsx b/exercises/01.latest-ref/01.problem/index.tsx index 218105b6..f5e34493 100644 --- a/exercises/01.latest-ref/01.problem/index.tsx +++ b/exercises/01.latest-ref/01.problem/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo, useState } from 'react' import * as ReactDOM from 'react-dom/client' function debounce) => void>( @@ -24,16 +24,16 @@ function useDebounce) => unknown>( // πŸ’° you'll need to pass an annonymous function to debounce. Do *not* // simply change this to `debounce(latestCallbackRef.current, delay)` // as that won't work. Can you think of why? - return React.useMemo(() => debounce(callback, delay), [callback, delay]) + return useMemo(() => debounce(callback, delay), [callback, delay]) } function App() { - const [step, setStep] = React.useState(1) - const [count, setCount] = React.useState(0) + const [step, setStep] = useState(1) + const [count, setCount] = useState(0) // πŸ¦‰ feel free to swap these two implementations and see they don't make // any difference to the user experience - // const increment = React.useCallback(() => setCount(c => c + step), [step]) + // const increment = useMemoCallback(() => setCount(c => c + step), [step]) const increment = () => setCount(c => c + step) const debouncedIncrement = useDebounce(increment, 3000) return ( diff --git a/exercises/01.latest-ref/01.solution/increments.test.tsx b/exercises/01.latest-ref/01.solution/increments.test.tsx index e7b324b2..a2e1c934 100644 --- a/exercises/01.latest-ref/01.solution/increments.test.tsx +++ b/exercises/01.latest-ref/01.solution/increments.test.tsx @@ -1,6 +1,6 @@ +import { expect, testStep } from '@kentcdodds/workshop-utils/test' import { waitFor, within } from '@testing-library/dom' -import { userEvent } from '~/shared/user-event.cjs' -import { expect, testStep } from '@kentcdodds/workshop-app/test' +import { userEvent } from '#shared/user-event.cjs' import '.' const screen = within(document.body) diff --git a/exercises/01.latest-ref/01.solution/index.tsx b/exercises/01.latest-ref/01.solution/index.tsx index 1b1c453e..c55a4b5f 100644 --- a/exercises/01.latest-ref/01.solution/index.tsx +++ b/exercises/01.latest-ref/01.solution/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import * as ReactDOM from 'react-dom/client' function debounce) => void>( @@ -18,19 +18,19 @@ function useDebounce) => unknown>( callback: Callback, delay: number, ) { - const callbackRef = React.useRef(callback) - React.useEffect(() => { + const callbackRef = useRef(callback) + useEffect(() => { callbackRef.current = callback }) - return React.useMemo( + return useMemo( () => debounce((...args) => callbackRef.current(...args), delay), [delay], ) } function App() { - const [step, setStep] = React.useState(1) - const [count, setCount] = React.useState(0) + const [step, setStep] = useState(1) + const [count, setCount] = useState(0) const increment = () => setCount(c => c + step) const debouncedIncrement = useDebounce(increment, 3000) return ( diff --git a/exercises/01.latest-ref/01.solution/step-change.test.tsx b/exercises/01.latest-ref/01.solution/step-change.test.tsx index 68595af6..a840dbfc 100644 --- a/exercises/01.latest-ref/01.solution/step-change.test.tsx +++ b/exercises/01.latest-ref/01.solution/step-change.test.tsx @@ -1,6 +1,6 @@ -import { waitFor, within, fireEvent } from '@testing-library/dom' -import { userEvent } from '~/shared/user-event.cjs' -import { expect, testStep } from '@kentcdodds/workshop-app/test' +import { expect, testStep } from '@kentcdodds/workshop-utils/test' +import { fireEvent, waitFor, within } from '@testing-library/dom' +import { userEvent } from '#shared/user-event.cjs' import '.' const screen = within(document.body) diff --git a/exercises/01.latest-ref/README.mdx b/exercises/01.latest-ref/README.mdx index 50ce3356..3ba63f53 100644 --- a/exercises/01.latest-ref/README.mdx +++ b/exercises/01.latest-ref/README.mdx @@ -84,7 +84,7 @@ So let's rewrite the example above with hooks: ```tsx function PokemonFeeder({ pokemon }) { - const [selectedPokemonFood, setSelectedPokemonFood] = React.useState(null) + const [selectedPokemonFood, setSelectedPokemonFood] = useState(null) const feedPokemon = async () => { const canEat = await pokemon.canEat(selectedPokemonFood) if (canEat) { @@ -107,14 +107,14 @@ to **ref**erence the latest version of a value. `useRef` to the rescue! ```tsx function PokemonFeeder({ pokemon }) { - const [selectedPokemonFood, setSelectedPokemonFood] = React.useState(null) - const latestPokemonRef = React.useRef(pokemon) - const latestSelectedPokemonFoodRef = React.useRef(selectedPokemonFood) + const [selectedPokemonFood, setSelectedPokemonFood] = useState(null) + const latestPokemonRef = useRef(pokemon) + const latestSelectedPokemonFoodRef = useRef(selectedPokemonFood) // why is the useEffect necessary? Because side-effects run in the function // body of your component can lead to some pretty confusing bugs. Just keep // your function body free of side-effects and you'll be better off. - React.useEffect(() => { + useEffect(() => { latestPokemonRef.current = pokemon latestSelectedPokemonFoodRef.current = selectedPokemonFood // Wondering why we have no dependency list? Do we really need it? @@ -152,18 +152,18 @@ referencing it in the dependency list. For example: ```tsx function useExampleOne(callback) { - React.useEffect(() => { + useEffect(() => { callback() }, [callback]) // <-- have to include the callback in the dep array } function useExampleTwo(callback) { - const latestCallbackRef = React.useRef(callback) - React.useEffect(() => { + const latestCallbackRef = useRef(callback) + useEffect(() => { latestCallbackRef.current = callback }) - React.useEffect(() => { + useEffect(() => { latestCallbackRef.current() }, []) // <-- don't have to include the callback in the dep array } diff --git a/exercises/02.composition/01.problem/index.tsx b/exercises/02.composition/01.problem/index.tsx index 69348151..a937374b 100644 --- a/exercises/02.composition/01.problem/index.tsx +++ b/exercises/02.composition/01.problem/index.tsx @@ -1,15 +1,16 @@ -import * as React from 'react' +import { useState } from 'react' import * as ReactDOM from 'react-dom/client' -import { allPokemon, PokemonDataView } from '~/shared/pokemon.tsx' -import type { PokemonData, User } from '~/shared/types.tsx' +import { PokemonDataView, allPokemon } from '#shared/pokemon.tsx' +import { type PokemonData, type User } from '#shared/types.tsx' function App() { - const [user] = React.useState({ name: 'Kody', image: '/img/kody.png' }) - const [pokemonList] = React.useState>(() => + const [user] = useState({ name: 'Kody', image: '/img/kody.png' }) + const [pokemonList] = useState>(() => Object.values(allPokemon), ) - const [selectedPokemon, setSelectedPokemon] = - React.useState(null) + const [selectedPokemon, setSelectedPokemon] = useState( + null, + ) return (
({ name: 'Kody', image: '/img/kody.png' }) - const [pokemonList] = React.useState>(() => + const [user] = useState({ name: 'Kody', image: '/img/kody.png' }) + const [pokemonList] = useState>(() => Object.values(allPokemon), ) - const [selectedPokemon, setSelectedPokemon] = - React.useState(null) + const [selectedPokemon, setSelectedPokemon] = useState( + null, + ) return (
setCount(c => c + 1) return } @@ -54,7 +54,7 @@ without losing the flexibility we're hoping for. ```tsx function App() { - const [count, setCount] = React.useState(0) + const [count, setCount] = useState(0) const increment = () => setCount(c => c + 1) return ( The button is on The button is off -
- -
+
) diff --git a/exercises/03.compound-components/01.problem/toggle.tsx b/exercises/03.compound-components/01.problem/toggle.tsx index 967ffe2a..c9b50a4f 100644 --- a/exercises/03.compound-components/01.problem/toggle.tsx +++ b/exercises/03.compound-components/01.problem/toggle.tsx @@ -1,5 +1,5 @@ -import * as React from 'react' -import { Switch } from '~/shared/switch.tsx' +import { useState } from 'react' +import { Switch } from '#shared/switch.tsx' // 🐨 create your ToggleContext context here // πŸ“œ https://reactjs.org/docs/context.html#reactcreatecontext @@ -8,7 +8,7 @@ import { Switch } from '~/shared/switch.tsx' // but because we must initialize it to `undefined`, you need to union that with `undefined` export function Toggle({ children }: { children: React.ReactNode }) { - const [on, setOn] = React.useState(false) + const [on, setOn] = useState(false) const toggle = () => setOn(!on) // πŸ’£ remove this and instead return where @@ -19,8 +19,8 @@ export function Toggle({ children }: { children: React.ReactNode }) { export function ToggleOn({ children }: { children: React.ReactNode }) { // 🐨 instead of this constant value, we'll need to get that from - // React.useContext(ToggleContext) - // πŸ“œ https://reactjs.org/docs/hooks-reference.html#usecontext + // use(ToggleContext) + // πŸ“œ https://reactjs.org/docs/hooks-reference.html#use const on = false return <>{on ? children : null} } @@ -34,7 +34,7 @@ export function ToggleOff({ children }: { children: React.ReactNode }) { export function ToggleButton( props: Omit, 'on' | 'onClick'>, ) { - // 🐨 get `on` and `toggle` from the ToggleContext with `useContext` + // 🐨 get `on` and `toggle` from the ToggleContext with `use` const on = false const toggle = () => {} return diff --git a/exercises/03.compound-components/01.solution/app.tsx b/exercises/03.compound-components/01.solution/app.tsx index 807c8072..0544e966 100644 --- a/exercises/03.compound-components/01.solution/app.tsx +++ b/exercises/03.compound-components/01.solution/app.tsx @@ -6,9 +6,7 @@ export function App() { The button is on The button is off -
- -
+
) diff --git a/exercises/03.compound-components/01.solution/toggle.test.tsx b/exercises/03.compound-components/01.solution/toggle.test.tsx index fd0787e9..179cd417 100644 --- a/exercises/03.compound-components/01.solution/toggle.test.tsx +++ b/exercises/03.compound-components/01.solution/toggle.test.tsx @@ -1,4 +1,4 @@ -import { verifySimpleToggleWithText } from '~/shared/toggle.test.tsx' +import { verifySimpleToggleWithText } from '#shared/toggle.test.tsx' import '.' await verifySimpleToggleWithText() diff --git a/exercises/03.compound-components/01.solution/toggle.tsx b/exercises/03.compound-components/01.solution/toggle.tsx index d13df3fe..95189164 100644 --- a/exercises/03.compound-components/01.solution/toggle.tsx +++ b/exercises/03.compound-components/01.solution/toggle.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' -import { Switch } from '~/shared/switch.tsx' +import { use, useState } from 'react' +import { Switch } from '#shared/switch.tsx' type ToggleValue = { on: boolean; toggle: () => void } const ToggleContext = React.createContext(undefined) ToggleContext.displayName = 'ToggleContext' export function Toggle({ children }: { children: React.ReactNode }) { - const [on, setOn] = React.useState(false) + const [on, setOn] = useState(false) const toggle = () => setOn(!on) return ( @@ -17,18 +17,18 @@ export function Toggle({ children }: { children: React.ReactNode }) { } export function ToggleOn({ children }: { children: React.ReactNode }) { - const { on } = React.useContext(ToggleContext)! + const { on } = use(ToggleContext)! return <>{on ? children : null} } export function ToggleOff({ children }: { children: React.ReactNode }) { - const { on } = React.useContext(ToggleContext)! + const { on } = use(ToggleContext)! return <>{on ? null : children} } export function ToggleButton({ ...props }: Omit, 'on' | 'onClick'>) { - const { on, toggle } = React.useContext(ToggleContext)! + const { on, toggle } = use(ToggleContext)! return } diff --git a/exercises/03.compound-components/02.problem.validation/app.tsx b/exercises/03.compound-components/02.problem.validation/app.tsx index 807c8072..0544e966 100644 --- a/exercises/03.compound-components/02.problem.validation/app.tsx +++ b/exercises/03.compound-components/02.problem.validation/app.tsx @@ -6,9 +6,7 @@ export function App() { The button is on The button is off -
- -
+
) diff --git a/exercises/03.compound-components/02.problem.validation/toggle.tsx b/exercises/03.compound-components/02.problem.validation/toggle.tsx index d13df3fe..95189164 100644 --- a/exercises/03.compound-components/02.problem.validation/toggle.tsx +++ b/exercises/03.compound-components/02.problem.validation/toggle.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' -import { Switch } from '~/shared/switch.tsx' +import { use, useState } from 'react' +import { Switch } from '#shared/switch.tsx' type ToggleValue = { on: boolean; toggle: () => void } const ToggleContext = React.createContext(undefined) ToggleContext.displayName = 'ToggleContext' export function Toggle({ children }: { children: React.ReactNode }) { - const [on, setOn] = React.useState(false) + const [on, setOn] = useState(false) const toggle = () => setOn(!on) return ( @@ -17,18 +17,18 @@ export function Toggle({ children }: { children: React.ReactNode }) { } export function ToggleOn({ children }: { children: React.ReactNode }) { - const { on } = React.useContext(ToggleContext)! + const { on } = use(ToggleContext)! return <>{on ? children : null} } export function ToggleOff({ children }: { children: React.ReactNode }) { - const { on } = React.useContext(ToggleContext)! + const { on } = use(ToggleContext)! return <>{on ? null : children} } export function ToggleButton({ ...props }: Omit, 'on' | 'onClick'>) { - const { on, toggle } = React.useContext(ToggleContext)! + const { on, toggle } = use(ToggleContext)! return } diff --git a/exercises/03.compound-components/02.solution.validation/app.tsx b/exercises/03.compound-components/02.solution.validation/app.tsx index 807c8072..34deeebe 100644 --- a/exercises/03.compound-components/02.solution.validation/app.tsx +++ b/exercises/03.compound-components/02.solution.validation/app.tsx @@ -1,4 +1,4 @@ -import { Toggle, ToggleOn, ToggleOff, ToggleButton } from './toggle.tsx' +import { Toggle, ToggleButton, ToggleOff, ToggleOn } from './toggle.tsx' export function App() { return ( @@ -6,9 +6,7 @@ export function App() { The button is on The button is off -
- -
+
) diff --git a/exercises/03.compound-components/02.solution.validation/toggle.test.tsx b/exercises/03.compound-components/02.solution.validation/toggle.test.tsx index fd0787e9..179cd417 100644 --- a/exercises/03.compound-components/02.solution.validation/toggle.test.tsx +++ b/exercises/03.compound-components/02.solution.validation/toggle.test.tsx @@ -1,4 +1,4 @@ -import { verifySimpleToggleWithText } from '~/shared/toggle.test.tsx' +import { verifySimpleToggleWithText } from '#shared/toggle.test.tsx' import '.' await verifySimpleToggleWithText() diff --git a/exercises/03.compound-components/02.solution.validation/toggle.tsx b/exercises/03.compound-components/02.solution.validation/toggle.tsx index f455c279..e4920d3c 100644 --- a/exercises/03.compound-components/02.solution.validation/toggle.tsx +++ b/exercises/03.compound-components/02.solution.validation/toggle.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' -import { Switch } from '~/shared/switch.tsx' +import { use, useState } from 'react' +import { Switch } from '#shared/switch.tsx' type ToggleValue = { on: boolean; toggle: () => void } const ToggleContext = React.createContext(undefined) ToggleContext.displayName = 'ToggleContext' export function Toggle({ children }: { children: React.ReactNode }) { - const [on, setOn] = React.useState(false) + const [on, setOn] = useState(false) const toggle = () => setOn(!on) return ( @@ -17,7 +17,7 @@ export function Toggle({ children }: { children: React.ReactNode }) { } function useToggle() { - const context = React.useContext(ToggleContext) + const context = use(ToggleContext) if (context === undefined) { throw new Error( 'Cannot find ToggleContext. All Toggle components must be rendered within ', diff --git a/exercises/03.compound-components/02.solution.validation/validation.test.tsx b/exercises/03.compound-components/02.solution.validation/validation.test.tsx index 10a19a3f..ca335c78 100644 --- a/exercises/03.compound-components/02.solution.validation/validation.test.tsx +++ b/exercises/03.compound-components/02.solution.validation/validation.test.tsx @@ -1,5 +1,5 @@ +import { expect, testStep } from '@kentcdodds/workshop-utils/test' import { render } from '@testing-library/react' -import { expect, testStep } from '@kentcdodds/workshop-app/test' import { ToggleButton, ToggleOff, ToggleOn } from './toggle.tsx' const expectedErrorMessage = diff --git a/exercises/04.prop-getters/01.solution.collections/toggle.test.tsx b/exercises/04.prop-getters/01.solution.collections/toggle.test.tsx deleted file mode 100644 index f261fc3c..00000000 --- a/exercises/04.prop-getters/01.solution.collections/toggle.test.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { verifySimpleToggle } from '~/shared/toggle.test.tsx' -import '.' - -await verifySimpleToggle() diff --git a/exercises/04.prop-getters/02.solution.getters/toggle.test.tsx b/exercises/04.prop-getters/02.solution.getters/toggle.test.tsx deleted file mode 100644 index f261fc3c..00000000 --- a/exercises/04.prop-getters/02.solution.getters/toggle.test.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import { verifySimpleToggle } from '~/shared/toggle.test.tsx' -import '.' - -await verifySimpleToggle() diff --git a/exercises/04.slots/01.problem/README.mdx b/exercises/04.slots/01.problem/README.mdx new file mode 100644 index 00000000..0b5e2dc8 --- /dev/null +++ b/exercises/04.slots/01.problem/README.mdx @@ -0,0 +1 @@ +# Slot Prop diff --git a/exercises/04.slots/01.problem/app.tsx b/exercises/04.slots/01.problem/app.tsx new file mode 100644 index 00000000..a7f61ea8 --- /dev/null +++ b/exercises/04.slots/01.problem/app.tsx @@ -0,0 +1,27 @@ +import { useId } from 'react' +import { Input, Label } from './slots.tsx' +import { TextField } from './text-field.tsx' +import { Toggle, ToggleButton, ToggleOff, ToggleOn } from './toggle.tsx' + +export function App() { + const partyModeId = useId() + return ( +
+
+ + + + Let's party πŸ₯³ + Sad town 😭 + +
+
+
+ + + + +
+
+ ) +} diff --git a/examples/resetting-via-key-prop/index.css b/exercises/04.slots/01.problem/index.css similarity index 100% rename from examples/resetting-via-key-prop/index.css rename to exercises/04.slots/01.problem/index.css diff --git a/exercises/04.prop-getters/01.problem.collections/index.tsx b/exercises/04.slots/01.problem/index.tsx similarity index 100% rename from exercises/04.prop-getters/01.problem.collections/index.tsx rename to exercises/04.slots/01.problem/index.tsx diff --git a/exercises/04.slots/01.problem/slots.tsx b/exercises/04.slots/01.problem/slots.tsx new file mode 100644 index 00000000..6fdd9e52 --- /dev/null +++ b/exercises/04.slots/01.problem/slots.tsx @@ -0,0 +1,7 @@ +export function Label(props: React.ComponentProps<'label'>) { + return