From 8534ff344843923de7be78917c3b6db2300033ee Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Mon, 4 Mar 2024 15:25:31 -0700 Subject: [PATCH] progress on exercise 2 --- .../04.solution.init/README.mdx | 3 + exercises/01.managing-ui-state/FINISHED.mdx | 3 + .../01.problem.effects/README.mdx | 1 - .../01.problem.effects/index.css | 89 ------------ .../01.problem.effects/index.tsx | 121 ---------------- .../01.solution.effects/README.mdx | 1 - .../01.solution.effects/index.css | 89 ------------ .../01.solution.effects/index.tsx | 116 --------------- .../02.problem.deps/README.mdx | 1 - .../02.problem.deps/index.css | 89 ------------ .../02.problem.deps/index.tsx | 120 ---------------- .../02.solution.deps/README.mdx | 1 - .../02.solution.deps/index.css | 89 ------------ .../02.solution.deps/index.tsx | 113 --------------- .../03.problem.cleanup/README.mdx | 1 - .../03.problem.cleanup/index.css | 89 ------------ .../03.problem.cleanup/index.tsx | 135 ----------------- .../03.solution.cleanup/README.mdx | 1 - .../03.solution.cleanup/index.css | 89 ------------ .../03.solution.cleanup/index.tsx | 136 ------------------ .../02.sychronizing-side-effects/FINISHED.mdx | 1 - .../02.sychronizing-side-effects/README.mdx | 27 ---- 22 files changed, 6 insertions(+), 1309 deletions(-) delete mode 100644 exercises/02.sychronizing-side-effects/01.problem.effects/README.mdx delete mode 100644 exercises/02.sychronizing-side-effects/01.problem.effects/index.css delete mode 100644 exercises/02.sychronizing-side-effects/01.problem.effects/index.tsx delete mode 100644 exercises/02.sychronizing-side-effects/01.solution.effects/README.mdx delete mode 100644 exercises/02.sychronizing-side-effects/01.solution.effects/index.css delete mode 100644 exercises/02.sychronizing-side-effects/01.solution.effects/index.tsx delete mode 100644 exercises/02.sychronizing-side-effects/02.problem.deps/README.mdx delete mode 100644 exercises/02.sychronizing-side-effects/02.problem.deps/index.css delete mode 100644 exercises/02.sychronizing-side-effects/02.problem.deps/index.tsx delete mode 100644 exercises/02.sychronizing-side-effects/02.solution.deps/README.mdx delete mode 100644 exercises/02.sychronizing-side-effects/02.solution.deps/index.css delete mode 100644 exercises/02.sychronizing-side-effects/02.solution.deps/index.tsx delete mode 100644 exercises/02.sychronizing-side-effects/03.problem.cleanup/README.mdx delete mode 100644 exercises/02.sychronizing-side-effects/03.problem.cleanup/index.css delete mode 100644 exercises/02.sychronizing-side-effects/03.problem.cleanup/index.tsx delete mode 100644 exercises/02.sychronizing-side-effects/03.solution.cleanup/README.mdx delete mode 100644 exercises/02.sychronizing-side-effects/03.solution.cleanup/index.css delete mode 100644 exercises/02.sychronizing-side-effects/03.solution.cleanup/index.tsx delete mode 100644 exercises/02.sychronizing-side-effects/FINISHED.mdx delete mode 100644 exercises/02.sychronizing-side-effects/README.mdx diff --git a/exercises/01.managing-ui-state/04.solution.init/README.mdx b/exercises/01.managing-ui-state/04.solution.init/README.mdx index 13c584527..6fdaea566 100644 --- a/exercises/01.managing-ui-state/04.solution.init/README.mdx +++ b/exercises/01.managing-ui-state/04.solution.init/README.mdx @@ -1 +1,4 @@ # Initialize State + +👨‍💼 Great! Now our users can share links to this page that initialize the search +to something specific. diff --git a/exercises/01.managing-ui-state/FINISHED.mdx b/exercises/01.managing-ui-state/FINISHED.mdx index 7c9eee365..9133c4660 100644 --- a/exercises/01.managing-ui-state/FINISHED.mdx +++ b/exercises/01.managing-ui-state/FINISHED.mdx @@ -1 +1,4 @@ # Managing UI State + +👨‍💼 Great work! You now know how to manage client-side component state in a React +application! diff --git a/exercises/02.sychronizing-side-effects/01.problem.effects/README.mdx b/exercises/02.sychronizing-side-effects/01.problem.effects/README.mdx deleted file mode 100644 index 412c32879..000000000 --- a/exercises/02.sychronizing-side-effects/01.problem.effects/README.mdx +++ /dev/null @@ -1 +0,0 @@ -# useEffect diff --git a/exercises/02.sychronizing-side-effects/01.problem.effects/index.css b/exercises/02.sychronizing-side-effects/01.problem.effects/index.css deleted file mode 100644 index 0cdfd44a3..000000000 --- a/exercises/02.sychronizing-side-effects/01.problem.effects/index.css +++ /dev/null @@ -1,89 +0,0 @@ -html, -body { - margin: 0; -} - -.app { - margin: 40px auto; - max-width: 1024px; - form { - text-align: center; - } -} - -.post-list { - list-style: none; - padding: 0; - display: flex; - gap: 20px; - flex-wrap: wrap; - justify-content: center; - li { - position: relative; - border-radius: 0.5rem; - overflow: hidden; - border: 1px solid #ddd; - width: 320px; - transition: transform 0.2s ease-in-out; - a { - text-decoration: none; - color: unset; - } - - &:hover, - &:has(*:focus), - &:has(*:active) { - transform: translate(0px, -6px); - } - - .post-image { - display: block; - width: 100%; - height: 200px; - } - - button { - position: absolute; - font-size: 1.5rem; - top: 20px; - right: 20px; - background: transparent; - border: none; - outline: none; - &:hover, - &:focus, - &:active { - animation: pulse 1.5s infinite; - } - } - - a { - padding: 10px 10px; - display: flex; - gap: 8px; - flex-direction: column; - h2 { - margin: 0; - font-size: 1.5rem; - font-weight: bold; - } - p { - margin: 0; - font-size: 1rem; - color: #666; - } - } - } -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.3); - } - 100% { - transform: scale(1); - } -} diff --git a/exercises/02.sychronizing-side-effects/01.problem.effects/index.tsx b/exercises/02.sychronizing-side-effects/01.problem.effects/index.tsx deleted file mode 100644 index a836e4cd8..000000000 --- a/exercises/02.sychronizing-side-effects/01.problem.effects/index.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useState } from 'react' -import * as ReactDOM from 'react-dom/client' -import { generateGradient, getMatchingPosts } from '#shared/blog-posts' -import { setGlobalSearchParams } from '#shared/utils' - -function App() { - // NOTE: this will not work with server rendering, but in a real app you can - // use react-router's useSearchParams instead - const params = new URLSearchParams(window.location.search) - // 🐨 create a function called getQueryParam that returns the query param - // (and falls back to an empty string) - const [query, setQuery] = useState(params.get('query') ?? '') - - const words = query.split(' ') - - const dogChecked = words.includes('dog') - const catChecked = words.includes('cat') - const caterpillarChecked = words.includes('caterpillar') - - // 🐨 add a useEffect(() => {}, []) call here - // 🦉 if you're familiar with the dependency array problem we have, don't - // spoil it for everyone else! We'll get to it soon enough! - // 🐨 in the useEffect callback, subscribe to window's popstate event - // 🐨 your event handler should call setQuery to getQueryParam() - // 📜 https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - // 🐨 the second argument to useEffect should be an empty array - - function handleCheck(tag: string, checked: boolean) { - const newWords = checked ? [...words, tag] : words.filter(w => w !== tag) - setQuery(newWords.filter(Boolean).join(' ').trim()) - } - - return ( -
-
{ - e.preventDefault() - setGlobalSearchParams({ query }) - }} - > -
- - setQuery(e.currentTarget.value)} - /> -
-
- - - -
- -
- -
- ) -} - -function MatchingPosts({ query }: { query: string }) { - const matchingPosts = getMatchingPosts(query) - - return ( - - ) -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() - -// 🤫 we'll talk about this later! -/* -eslint - react-hooks/exhaustive-deps: "off", -*/ diff --git a/exercises/02.sychronizing-side-effects/01.solution.effects/README.mdx b/exercises/02.sychronizing-side-effects/01.solution.effects/README.mdx deleted file mode 100644 index 412c32879..000000000 --- a/exercises/02.sychronizing-side-effects/01.solution.effects/README.mdx +++ /dev/null @@ -1 +0,0 @@ -# useEffect diff --git a/exercises/02.sychronizing-side-effects/01.solution.effects/index.css b/exercises/02.sychronizing-side-effects/01.solution.effects/index.css deleted file mode 100644 index 0cdfd44a3..000000000 --- a/exercises/02.sychronizing-side-effects/01.solution.effects/index.css +++ /dev/null @@ -1,89 +0,0 @@ -html, -body { - margin: 0; -} - -.app { - margin: 40px auto; - max-width: 1024px; - form { - text-align: center; - } -} - -.post-list { - list-style: none; - padding: 0; - display: flex; - gap: 20px; - flex-wrap: wrap; - justify-content: center; - li { - position: relative; - border-radius: 0.5rem; - overflow: hidden; - border: 1px solid #ddd; - width: 320px; - transition: transform 0.2s ease-in-out; - a { - text-decoration: none; - color: unset; - } - - &:hover, - &:has(*:focus), - &:has(*:active) { - transform: translate(0px, -6px); - } - - .post-image { - display: block; - width: 100%; - height: 200px; - } - - button { - position: absolute; - font-size: 1.5rem; - top: 20px; - right: 20px; - background: transparent; - border: none; - outline: none; - &:hover, - &:focus, - &:active { - animation: pulse 1.5s infinite; - } - } - - a { - padding: 10px 10px; - display: flex; - gap: 8px; - flex-direction: column; - h2 { - margin: 0; - font-size: 1.5rem; - font-weight: bold; - } - p { - margin: 0; - font-size: 1rem; - color: #666; - } - } - } -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.3); - } - 100% { - transform: scale(1); - } -} diff --git a/exercises/02.sychronizing-side-effects/01.solution.effects/index.tsx b/exercises/02.sychronizing-side-effects/01.solution.effects/index.tsx deleted file mode 100644 index 77e4d7254..000000000 --- a/exercises/02.sychronizing-side-effects/01.solution.effects/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useEffect, useState } from 'react' -import * as ReactDOM from 'react-dom/client' -import { generateGradient, getMatchingPosts } from '#shared/blog-posts' -import { setGlobalSearchParams } from '#shared/utils' - -function App() { - // NOTE: this will not work with server rendering, but in a real app you can - // use react-router's useSearchParams instead - const params = new URLSearchParams(window.location.search) - const getQueryParam = () => params.get('query') ?? '' - const [query, setQuery] = useState(getQueryParam) - - const words = query.split(' ') - - const dogChecked = words.includes('dog') - const catChecked = words.includes('cat') - const caterpillarChecked = words.includes('caterpillar') - - useEffect(() => { - window.addEventListener('popstate', () => setQuery(getQueryParam())) - }, []) - - function handleCheck(tag: string, checked: boolean) { - const newWords = checked ? [...words, tag] : words.filter(w => w !== tag) - setQuery(newWords.filter(Boolean).join(' ').trim()) - } - - return ( -
-
{ - e.preventDefault() - setGlobalSearchParams({ query }) - }} - > -
- - setQuery(e.currentTarget.value)} - /> -
-
- - - -
- -
- -
- ) -} - -function MatchingPosts({ query }: { query: string }) { - const matchingPosts = getMatchingPosts(query) - - return ( - - ) -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() - -// 🤫 we'll talk about this later! -/* -eslint - react-hooks/exhaustive-deps: "off", -*/ diff --git a/exercises/02.sychronizing-side-effects/02.problem.deps/README.mdx b/exercises/02.sychronizing-side-effects/02.problem.deps/README.mdx deleted file mode 100644 index 7b1d011a1..000000000 --- a/exercises/02.sychronizing-side-effects/02.problem.deps/README.mdx +++ /dev/null @@ -1 +0,0 @@ -# Stale Closures diff --git a/exercises/02.sychronizing-side-effects/02.problem.deps/index.css b/exercises/02.sychronizing-side-effects/02.problem.deps/index.css deleted file mode 100644 index 0cdfd44a3..000000000 --- a/exercises/02.sychronizing-side-effects/02.problem.deps/index.css +++ /dev/null @@ -1,89 +0,0 @@ -html, -body { - margin: 0; -} - -.app { - margin: 40px auto; - max-width: 1024px; - form { - text-align: center; - } -} - -.post-list { - list-style: none; - padding: 0; - display: flex; - gap: 20px; - flex-wrap: wrap; - justify-content: center; - li { - position: relative; - border-radius: 0.5rem; - overflow: hidden; - border: 1px solid #ddd; - width: 320px; - transition: transform 0.2s ease-in-out; - a { - text-decoration: none; - color: unset; - } - - &:hover, - &:has(*:focus), - &:has(*:active) { - transform: translate(0px, -6px); - } - - .post-image { - display: block; - width: 100%; - height: 200px; - } - - button { - position: absolute; - font-size: 1.5rem; - top: 20px; - right: 20px; - background: transparent; - border: none; - outline: none; - &:hover, - &:focus, - &:active { - animation: pulse 1.5s infinite; - } - } - - a { - padding: 10px 10px; - display: flex; - gap: 8px; - flex-direction: column; - h2 { - margin: 0; - font-size: 1.5rem; - font-weight: bold; - } - p { - margin: 0; - font-size: 1rem; - color: #666; - } - } - } -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.3); - } - 100% { - transform: scale(1); - } -} diff --git a/exercises/02.sychronizing-side-effects/02.problem.deps/index.tsx b/exercises/02.sychronizing-side-effects/02.problem.deps/index.tsx deleted file mode 100644 index b27188ae7..000000000 --- a/exercises/02.sychronizing-side-effects/02.problem.deps/index.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// 💣 Remove this comment to reveal the ESLint warning that would have saved us! -/* -eslint - react-hooks/exhaustive-deps: "off", -*/ -import { useEffect, useState } from 'react' -import * as ReactDOM from 'react-dom/client' -import { generateGradient, getMatchingPosts } from '#shared/blog-posts' -import { setGlobalSearchParams } from '#shared/utils' - -// 🐨 create a getQueryParam function which: -// 1. creates a new params object -// 2. gets the query from the params object - -function App() { - // NOTE: this will not work with server rendering, but in a real app you can - // use react-router's useSearchParams instead - // 💣 remove these two lines in favor of the getQueryParam function above - const params = new URLSearchParams(window.location.search) - const getQueryParam = () => params.get('query') ?? '' - const [query, setQuery] = useState(getQueryParam) - - const words = query.split(' ') - - const dogChecked = words.includes('dog') - const catChecked = words.includes('cat') - const caterpillarChecked = words.includes('caterpillar') - - useEffect(() => { - window.addEventListener('popstate', () => setQuery(getQueryParam())) - }, []) - - function handleCheck(tag: string, checked: boolean) { - const newWords = checked ? [...words, tag] : words.filter(w => w !== tag) - setQuery(newWords.filter(Boolean).join(' ').trim()) - } - - return ( -
-
{ - e.preventDefault() - setGlobalSearchParams({ query }) - }} - > -
- - setQuery(e.currentTarget.value)} - /> -
-
- - - -
- -
- -
- ) -} - -function MatchingPosts({ query }: { query: string }) { - const matchingPosts = getMatchingPosts(query) - - return ( - - ) -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() diff --git a/exercises/02.sychronizing-side-effects/02.solution.deps/README.mdx b/exercises/02.sychronizing-side-effects/02.solution.deps/README.mdx deleted file mode 100644 index 7b1d011a1..000000000 --- a/exercises/02.sychronizing-side-effects/02.solution.deps/README.mdx +++ /dev/null @@ -1 +0,0 @@ -# Stale Closures diff --git a/exercises/02.sychronizing-side-effects/02.solution.deps/index.css b/exercises/02.sychronizing-side-effects/02.solution.deps/index.css deleted file mode 100644 index 0cdfd44a3..000000000 --- a/exercises/02.sychronizing-side-effects/02.solution.deps/index.css +++ /dev/null @@ -1,89 +0,0 @@ -html, -body { - margin: 0; -} - -.app { - margin: 40px auto; - max-width: 1024px; - form { - text-align: center; - } -} - -.post-list { - list-style: none; - padding: 0; - display: flex; - gap: 20px; - flex-wrap: wrap; - justify-content: center; - li { - position: relative; - border-radius: 0.5rem; - overflow: hidden; - border: 1px solid #ddd; - width: 320px; - transition: transform 0.2s ease-in-out; - a { - text-decoration: none; - color: unset; - } - - &:hover, - &:has(*:focus), - &:has(*:active) { - transform: translate(0px, -6px); - } - - .post-image { - display: block; - width: 100%; - height: 200px; - } - - button { - position: absolute; - font-size: 1.5rem; - top: 20px; - right: 20px; - background: transparent; - border: none; - outline: none; - &:hover, - &:focus, - &:active { - animation: pulse 1.5s infinite; - } - } - - a { - padding: 10px 10px; - display: flex; - gap: 8px; - flex-direction: column; - h2 { - margin: 0; - font-size: 1.5rem; - font-weight: bold; - } - p { - margin: 0; - font-size: 1rem; - color: #666; - } - } - } -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.3); - } - 100% { - transform: scale(1); - } -} diff --git a/exercises/02.sychronizing-side-effects/02.solution.deps/index.tsx b/exercises/02.sychronizing-side-effects/02.solution.deps/index.tsx deleted file mode 100644 index 1eaba14ee..000000000 --- a/exercises/02.sychronizing-side-effects/02.solution.deps/index.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { useEffect, useState } from 'react' -import * as ReactDOM from 'react-dom/client' -import { generateGradient, getMatchingPosts } from '#shared/blog-posts' -import { setGlobalSearchParams } from '#shared/utils' - -function getQueryParam() { - const params = new URLSearchParams(window.location.search) - return params.get('query') ?? '' -} - -function App() { - // NOTE: this will not work with server rendering, but in a real app you can - // use react-router's useSearchParams instead - const [query, setQuery] = useState(getQueryParam) - - const words = query.split(' ') - - const dogChecked = words.includes('dog') - const catChecked = words.includes('cat') - const caterpillarChecked = words.includes('caterpillar') - - useEffect(() => { - window.addEventListener('popstate', () => setQuery(getQueryParam())) - }, []) - - function handleCheck(tag: string, checked: boolean) { - const newWords = checked ? [...words, tag] : words.filter(w => w !== tag) - setQuery(newWords.filter(Boolean).join(' ').trim()) - } - - return ( -
-
{ - e.preventDefault() - setGlobalSearchParams({ query }) - }} - > -
- - setQuery(e.currentTarget.value)} - /> -
-
- - - -
- -
- -
- ) -} - -function MatchingPosts({ query }: { query: string }) { - const matchingPosts = getMatchingPosts(query) - - return ( - - ) -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() diff --git a/exercises/02.sychronizing-side-effects/03.problem.cleanup/README.mdx b/exercises/02.sychronizing-side-effects/03.problem.cleanup/README.mdx deleted file mode 100644 index fc37e6464..000000000 --- a/exercises/02.sychronizing-side-effects/03.problem.cleanup/README.mdx +++ /dev/null @@ -1 +0,0 @@ -# Effect Cleanup diff --git a/exercises/02.sychronizing-side-effects/03.problem.cleanup/index.css b/exercises/02.sychronizing-side-effects/03.problem.cleanup/index.css deleted file mode 100644 index 0cdfd44a3..000000000 --- a/exercises/02.sychronizing-side-effects/03.problem.cleanup/index.css +++ /dev/null @@ -1,89 +0,0 @@ -html, -body { - margin: 0; -} - -.app { - margin: 40px auto; - max-width: 1024px; - form { - text-align: center; - } -} - -.post-list { - list-style: none; - padding: 0; - display: flex; - gap: 20px; - flex-wrap: wrap; - justify-content: center; - li { - position: relative; - border-radius: 0.5rem; - overflow: hidden; - border: 1px solid #ddd; - width: 320px; - transition: transform 0.2s ease-in-out; - a { - text-decoration: none; - color: unset; - } - - &:hover, - &:has(*:focus), - &:has(*:active) { - transform: translate(0px, -6px); - } - - .post-image { - display: block; - width: 100%; - height: 200px; - } - - button { - position: absolute; - font-size: 1.5rem; - top: 20px; - right: 20px; - background: transparent; - border: none; - outline: none; - &:hover, - &:focus, - &:active { - animation: pulse 1.5s infinite; - } - } - - a { - padding: 10px 10px; - display: flex; - gap: 8px; - flex-direction: column; - h2 { - margin: 0; - font-size: 1.5rem; - font-weight: bold; - } - p { - margin: 0; - font-size: 1rem; - color: #666; - } - } - } -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.3); - } - 100% { - transform: scale(1); - } -} diff --git a/exercises/02.sychronizing-side-effects/03.problem.cleanup/index.tsx b/exercises/02.sychronizing-side-effects/03.problem.cleanup/index.tsx deleted file mode 100644 index 4edd0b759..000000000 --- a/exercises/02.sychronizing-side-effects/03.problem.cleanup/index.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { useEffect, useState } from 'react' -import * as ReactDOM from 'react-dom/client' -import { generateGradient, getMatchingPosts } from '#shared/blog-posts' -import { setGlobalSearchParams } from '#shared/utils' - -function getQueryParam() { - const params = new URLSearchParams(window.location.search) - return params.get('query') ?? '' -} - -function App() { - const [query, setQuery] = useState(getQueryParam) - - const words = query.split(' ') - - const dogChecked = words.includes('dog') - const catChecked = words.includes('cat') - const caterpillarChecked = words.includes('caterpillar') - - useEffect(() => { - // 🐨 extract your event handler here into a function called updateQuery - window.addEventListener('popstate', () => { - console.log('popstate event listener called') - setQuery(getQueryParam()) - }) - // 🐨 return a function which removes the popstate event listener - // 📜 https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener - }, []) - - function handleCheck(tag: string, checked: boolean) { - const newWords = checked ? [...words, tag] : words.filter(w => w !== tag) - setQuery(newWords.filter(Boolean).join(' ').trim()) - } - - return ( -
-
{ - e.preventDefault() - setGlobalSearchParams({ query }) - }} - > -
- - setQuery(e.currentTarget.value)} - /> -
-
- - - -
- -
- -
- ) -} - -function MatchingPosts({ query }: { query: string }) { - const matchingPosts = getMatchingPosts(query) - - return ( - - ) -} - -function DemoApp() { - const [showForm, setShowForm] = useState(true) - - return ( -
- - {showForm ? : null} -
- ) -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() diff --git a/exercises/02.sychronizing-side-effects/03.solution.cleanup/README.mdx b/exercises/02.sychronizing-side-effects/03.solution.cleanup/README.mdx deleted file mode 100644 index fc37e6464..000000000 --- a/exercises/02.sychronizing-side-effects/03.solution.cleanup/README.mdx +++ /dev/null @@ -1 +0,0 @@ -# Effect Cleanup diff --git a/exercises/02.sychronizing-side-effects/03.solution.cleanup/index.css b/exercises/02.sychronizing-side-effects/03.solution.cleanup/index.css deleted file mode 100644 index 0cdfd44a3..000000000 --- a/exercises/02.sychronizing-side-effects/03.solution.cleanup/index.css +++ /dev/null @@ -1,89 +0,0 @@ -html, -body { - margin: 0; -} - -.app { - margin: 40px auto; - max-width: 1024px; - form { - text-align: center; - } -} - -.post-list { - list-style: none; - padding: 0; - display: flex; - gap: 20px; - flex-wrap: wrap; - justify-content: center; - li { - position: relative; - border-radius: 0.5rem; - overflow: hidden; - border: 1px solid #ddd; - width: 320px; - transition: transform 0.2s ease-in-out; - a { - text-decoration: none; - color: unset; - } - - &:hover, - &:has(*:focus), - &:has(*:active) { - transform: translate(0px, -6px); - } - - .post-image { - display: block; - width: 100%; - height: 200px; - } - - button { - position: absolute; - font-size: 1.5rem; - top: 20px; - right: 20px; - background: transparent; - border: none; - outline: none; - &:hover, - &:focus, - &:active { - animation: pulse 1.5s infinite; - } - } - - a { - padding: 10px 10px; - display: flex; - gap: 8px; - flex-direction: column; - h2 { - margin: 0; - font-size: 1.5rem; - font-weight: bold; - } - p { - margin: 0; - font-size: 1rem; - color: #666; - } - } - } -} - -@keyframes pulse { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.3); - } - 100% { - transform: scale(1); - } -} diff --git a/exercises/02.sychronizing-side-effects/03.solution.cleanup/index.tsx b/exercises/02.sychronizing-side-effects/03.solution.cleanup/index.tsx deleted file mode 100644 index 2b4e94720..000000000 --- a/exercises/02.sychronizing-side-effects/03.solution.cleanup/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { useEffect, useState } from 'react' -import * as ReactDOM from 'react-dom/client' -import { generateGradient, getMatchingPosts } from '#shared/blog-posts' -import { setGlobalSearchParams } from '#shared/utils' - -function getQueryParam() { - const params = new URLSearchParams(window.location.search) - return params.get('query') ?? '' -} - -function App() { - const [query, setQuery] = useState(getQueryParam) - - const words = query.split(' ') - - const dogChecked = words.includes('dog') - const catChecked = words.includes('cat') - const caterpillarChecked = words.includes('caterpillar') - - useEffect(() => { - function updateQuery() { - console.log('popstate event listener called') - setQuery(getQueryParam()) - } - window.addEventListener('popstate', updateQuery) - return () => { - window.removeEventListener('popstate', updateQuery) - } - }, []) - - function handleCheck(tag: string, checked: boolean) { - const newWords = checked ? [...words, tag] : words.filter(w => w !== tag) - setQuery(newWords.filter(Boolean).join(' ').trim()) - } - - return ( -
-
{ - e.preventDefault() - setGlobalSearchParams({ query }) - }} - > -
- - setQuery(e.currentTarget.value)} - /> -
-
- - - -
- -
- -
- ) -} - -function MatchingPosts({ query }: { query: string }) { - const matchingPosts = getMatchingPosts(query) - - return ( - - ) -} - -function DemoApp() { - const [showForm, setShowForm] = useState(true) - - return ( -
- - {showForm ? : null} -
- ) -} - -const rootEl = document.createElement('div') -document.body.append(rootEl) -ReactDOM.createRoot(rootEl).render() diff --git a/exercises/02.sychronizing-side-effects/FINISHED.mdx b/exercises/02.sychronizing-side-effects/FINISHED.mdx deleted file mode 100644 index e1a234aff..000000000 --- a/exercises/02.sychronizing-side-effects/FINISHED.mdx +++ /dev/null @@ -1 +0,0 @@ -# Synchronizing Side-Effects diff --git a/exercises/02.sychronizing-side-effects/README.mdx b/exercises/02.sychronizing-side-effects/README.mdx deleted file mode 100644 index d677f5d48..000000000 --- a/exercises/02.sychronizing-side-effects/README.mdx +++ /dev/null @@ -1,27 +0,0 @@ -# Synchronizing Side-Effects - -`useEffect` is a built-in hook that allows you to run some custom code -after React renders (and re-renders) your component to the DOM. It accepts a -callback function which React will call after the DOM has been updated: - -```javascript -useEffect(() => { - // your side-effect code here. - // this is where you can interact with browser APIs for example -}) -``` - -`useState` is for managing our react component state and `useEffect` is for -synchronizing the our react component state with the everything outside our -react components. - -For example, things outside our react components include: - -- Browser APIs like local storage/document title/media devices/etc. -- Integrations with non-react code - -Check out [the React Flow diagram](https://github.com/donavon/hook-flow) below: - -![React Flow diagram showing mount, update, unmount](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/295689283-b9ecdd1d-ce28-446b-84ad-6b264d4be8e4.png) - -This will make more sense after finishing the exercise. So come back!