I am a grand child and I just pass things off to a button
diff --git a/exercises/02.composition/01.solution/README.mdx b/exercises/02.composition/01.solution/README.mdx
deleted file mode 100644
index eaf46f58..00000000
--- a/exercises/02.composition/01.solution/README.mdx
+++ /dev/null
@@ -1,6 +0,0 @@
-# Composition and Layout Components
-
-In this one we didn't actually change any visual behavior (the test was passing
-before your changes). But we hopefully demonstrated how restructuring your
-components can make it easier to maintain and help you avoid the prop drilling
-problem and reduce the amount you feel you need to use `use`.
diff --git a/exercises/01.latest-ref/01.problem/README.mdx b/exercises/02.latest-ref/01.problem.ref/README.mdx
similarity index 87%
rename from exercises/01.latest-ref/01.problem/README.mdx
rename to exercises/02.latest-ref/01.problem.ref/README.mdx
index e1297c8d..0af1bf34 100644
--- a/exercises/01.latest-ref/01.problem/README.mdx
+++ b/exercises/02.latest-ref/01.problem.ref/README.mdx
@@ -1,7 +1,9 @@
# 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
+
+
+๐จโ๐ผ 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
@@ -29,7 +31,7 @@ want to make it so this works:
(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
+Before continuing here, please familiarize yourself with the code to know how
it's implemented... Got it? Great, let's continue.
Right now, you can play around with two different problems with the way our
@@ -47,7 +49,7 @@ 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:
+Ultimately this is the bug our users experience:
- The user clicks the button
- The user updates the step value
@@ -68,7 +70,7 @@ you pass a memoized callback:
```ts
// option 2:
// ...
-const increment = useMemoCallback(() => setCount(c => c + step), [step])
+const increment = useCallback(() => setCount(c => c + step), [step])
const debouncedIncrement = useDebounce(increment, 3000)
// ...
```
diff --git a/exercises/01.latest-ref/01.problem/index.tsx b/exercises/02.latest-ref/01.problem.ref/index.tsx
similarity index 91%
rename from exercises/01.latest-ref/01.problem/index.tsx
rename to exercises/02.latest-ref/01.problem.ref/index.tsx
index f5e34493..804b3084 100644
--- a/exercises/01.latest-ref/01.problem/index.tsx
+++ b/exercises/02.latest-ref/01.problem.ref/index.tsx
@@ -21,7 +21,7 @@ function useDebounce
) => unknown>(
// ๐จ create a latest ref (via useRef and useEffect) here
// use the latest version of the callback here:
- // ๐ฐ you'll need to pass an annonymous function to debounce. Do *not*
+ // ๐ฐ you'll need to pass an anonymous function to debounce. Do *not*
// simply change this to `debounce(latestCallbackRef.current, delay)`
// as that won't work. Can you think of why?
return useMemo(() => debounce(callback, delay), [callback, delay])
@@ -33,7 +33,7 @@ function App() {
// ๐ฆ feel free to swap these two implementations and see they don't make
// any difference to the user experience
- // const increment = useMemoCallback(() => setCount(c => c + step), [step])
+ // const increment = useCallback(() => 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/README.mdx b/exercises/02.latest-ref/01.solution.ref/README.mdx
similarity index 60%
rename from exercises/01.latest-ref/01.solution/README.mdx
rename to exercises/02.latest-ref/01.solution.ref/README.mdx
index fe752ab5..d3264b29 100644
--- a/exercises/01.latest-ref/01.solution/README.mdx
+++ b/exercises/02.latest-ref/01.solution.ref/README.mdx
@@ -1,6 +1,8 @@
# Latest Ref
-Great work. If you've got a bit more time and you haven't already read this
+
+
+๐จโ๐ผ Great work. If you've got a bit more time and you haven't already read this
post, I suggest you give these a read:
- [How React Uses Closures to Avoid Bugs](https://epicreact.dev/how-react-uses-closures-to-avoid-bugs)
diff --git a/exercises/01.latest-ref/01.solution/increments.test.tsx b/exercises/02.latest-ref/01.solution.ref/increments.test.tsx
similarity index 86%
rename from exercises/01.latest-ref/01.solution/increments.test.tsx
rename to exercises/02.latest-ref/01.solution.ref/increments.test.tsx
index a2e1c934..0649314f 100644
--- a/exercises/01.latest-ref/01.solution/increments.test.tsx
+++ b/exercises/02.latest-ref/01.solution.ref/increments.test.tsx
@@ -1,6 +1,6 @@
-import { expect, testStep } from '@kentcdodds/workshop-utils/test'
+import { expect, testStep } from '@epic-web/workshop-utils/test'
import { waitFor, within } from '@testing-library/dom'
-import { userEvent } from '#shared/user-event.cjs'
+import { userEvent } from '@testing-library/user-event'
import '.'
const screen = within(document.body)
diff --git a/exercises/01.latest-ref/01.solution/index.tsx b/exercises/02.latest-ref/01.solution.ref/index.tsx
similarity index 100%
rename from exercises/01.latest-ref/01.solution/index.tsx
rename to exercises/02.latest-ref/01.solution.ref/index.tsx
diff --git a/exercises/01.latest-ref/01.solution/step-change.test.tsx b/exercises/02.latest-ref/01.solution.ref/step-change.test.tsx
similarity index 87%
rename from exercises/01.latest-ref/01.solution/step-change.test.tsx
rename to exercises/02.latest-ref/01.solution.ref/step-change.test.tsx
index a840dbfc..57a189e5 100644
--- a/exercises/01.latest-ref/01.solution/step-change.test.tsx
+++ b/exercises/02.latest-ref/01.solution.ref/step-change.test.tsx
@@ -1,6 +1,6 @@
-import { expect, testStep } from '@kentcdodds/workshop-utils/test'
+import { expect, testStep } from '@epic-web/workshop-utils/test'
import { fireEvent, waitFor, within } from '@testing-library/dom'
-import { userEvent } from '#shared/user-event.cjs'
+import { userEvent } from '@testing-library/user-event'
import '.'
const screen = within(document.body)
diff --git a/exercises/02.latest-ref/FINISHED.mdx b/exercises/02.latest-ref/FINISHED.mdx
new file mode 100644
index 00000000..2037f1a0
--- /dev/null
+++ b/exercises/02.latest-ref/FINISHED.mdx
@@ -0,0 +1,5 @@
+# Latest Ref
+
+
+
+๐จโ๐ผ Great job! You now know the latest ref pattern.
diff --git a/exercises/01.latest-ref/README.mdx b/exercises/02.latest-ref/README.mdx
similarity index 61%
rename from exercises/01.latest-ref/README.mdx
rename to exercises/02.latest-ref/README.mdx
index 3ba63f53..91b0d4f7 100644
--- a/exercises/01.latest-ref/README.mdx
+++ b/exercises/02.latest-ref/README.mdx
@@ -1,8 +1,12 @@
# Latest Ref
-**One liner:** The Latest Ref Pattern allows you to have a reference to the
-latest value of a prop, state, or callback without needing to list it in a
-dependency array when accessing it in a `useEffect`.
+
+
+
+ **One liner:** The Latest Ref Pattern allows you to have a reference to the
+ latest value of a prop, state, or callback without needing to list it in a
+ dependency array when accessing it in a `useEffect`.
+
When React introduced hooks it did more than give us an excellent primitive with
super abstract-ability powers. It also changed an important default that results
@@ -14,53 +18,47 @@ that you'd always get the latest value of state or props in your functions.
Let's explore an example:
```tsx
-class PokemonFeeder extends React.Component {
- state = { selectedPokemonFood: null }
- feedPokemon = async () => {
- const canEat = await this.props.pokemon.canEat(
- this.state.selectedPokemonFood,
- )
+class PetFeeder extends React.Component {
+ state = { selectedPetFood: null }
+ feedPet = async () => {
+ const canEat = await this.props.pet.canEat(this.state.selectedPetFood)
if (canEat) {
- this.props.pokemon.eat(this.state.selectedPokemonFood)
+ this.props.pet.eat(this.state.selectedPetFood)
}
}
render() {
return (
-
- this.setState({ selectedPokemonFood })
- }
+ this.setState({ selectedPetFood })}
/>
-
+
)
}
}
```
-Think about that `feedPokemon` function for a moment... What kinds of bugs can
-you spot with this implementation? Let me ask you a question. What would happen
-if the `pokemon.canEat` function took a couple seconds to resolve? What could
-the user do to cause a problem? If the user changed the `selectedPokemonFood`
-what could happen? Yeah! You could check whether your pidgey can eat worms but
-then actually feed it grass! Or what if while we're checking whether charizard
-can eat some candy the props changed now we're withholding the candy from a
-hungry pikachu! ๐ข
+Think about that `feedPet` function for a moment... What kinds of bugs can you
+spot with this implementation? Let me ask you a question. What would happen if
+the `pet.canEat` function took a couple seconds to resolve? What could the user
+do to cause a problem? If the user changed the `selectedPetFood` what could
+happen? Yeah! You could check whether your bird can eat worms but then actually
+feed it grass! Or what if while we're checking whether your dog can eat some
+candy the props changed now we're withholding the candy from a hungry crab! ๐ข
+(I mean, like crab-candy... I don't know if that's a thing... ๐
)
Can you imagine how we could side-step these issues? It's simple actually:
```ts
-class PokemonFeeder extends React.Component {
+class PetFeeder extends React.Component {
// ...
- feedPokemon = async () => {
- const { pokemon } = this.props
- const { selectedPokemonFood } = this.state
- const canEat = await pokemon.canEat(selectedPokemonFood)
+ feedPet = async () => {
+ const { pet } = this.props
+ const { selectedPetFood } = this.state
+ const canEat = await pet.canEat(selectedPetFood)
if (canEat) {
- pokemon.eat(selectedPokemonFood)
+ pet.eat(selectedPetFood)
}
}
// ...
@@ -83,18 +81,18 @@ avoid them, so you'll ship fewer (at least, that's been my experience).
So let's rewrite the example above with hooks:
```tsx
-function PokemonFeeder({ pokemon }) {
- const [selectedPokemonFood, setSelectedPokemonFood] = useState(null)
- const feedPokemon = async () => {
- const canEat = await pokemon.canEat(selectedPokemonFood)
+function PetFeeder({ pet }) {
+ const [selectedPetFood, setSelectedPetFood] = useState(null)
+ const feedPet = async () => {
+ const canEat = await pet.canEat(selectedPetFood)
if (canEat) {
- pokemon.eat(selectedPokemonFood)
+ pet.eat(selectedPetFood)
}
}
return (
-
setSelectedPokemonFood(food)} />
-
+ setSelectedPetFood(food)} />
+
)
}
@@ -106,33 +104,33 @@ behavior before? Could we make that work with hooks? Sure! We just need some way
to **ref**erence the latest version of a value. `useRef` to the rescue!
```tsx
-function PokemonFeeder({ pokemon }) {
- const [selectedPokemonFood, setSelectedPokemonFood] = useState(null)
- const latestPokemonRef = useRef(pokemon)
- const latestSelectedPokemonFoodRef = useRef(selectedPokemonFood)
+function PetFeeder({ pet }) {
+ const [selectedPetFood, setSelectedPetFood] = useState(null)
+ const latestPetRef = useRef(pet)
+ const latestSelectedPetFoodRef = useRef(selectedPetFood)
// 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.
useEffect(() => {
- latestPokemonRef.current = pokemon
- latestSelectedPokemonFoodRef.current = selectedPokemonFood
+ latestPetRef.current = pet
+ latestSelectedPetFoodRef.current = selectedPetFood
// Wondering why we have no dependency list? Do we really need it?
// Not really... So we don't bother.
})
- const feedPokemon = async () => {
- const canEat = await latestPokemonRef.current.canEat(
- latestSelectedPokemonFoodRef.current,
+ const feedPet = async () => {
+ const canEat = await latestPetRef.current.canEat(
+ latestSelectedPetFoodRef.current,
)
if (canEat) {
- latestPokemonRef.current.eat(latestSelectedPokemonFoodRef.current)
+ latestPetRef.current.eat(latestSelectedPetFoodRef.current)
}
}
return (
-
setSelectedPokemonFood(food)} />
-
+ setSelectedPetFood(food)} />
+
)
}
@@ -173,6 +171,9 @@ It's important that you understand the trade-offs here! Remember, when we do
this we're going back to the class component default. So just think about the
unexpected behavior's you'll get when you switch the default like this.
+๐ For more on hooks and closures, check
+[Getting Closure on React Hooks](https://www.swyx.io/hooks)
+
๐ For more on this subject, read
[How React Uses Closures to Avoid Bugs](https://epicreact.dev/how-react-uses-closures-to-avoid-bugs).
diff --git a/exercises/03.compound-components/01.problem/README.mdx b/exercises/03.compound-components/01.problem.context/README.mdx
similarity index 80%
rename from exercises/03.compound-components/01.problem/README.mdx
rename to exercises/03.compound-components/01.problem.context/README.mdx
index 050a22c9..6ef63c03 100644
--- a/exercises/03.compound-components/01.problem/README.mdx
+++ b/exercises/03.compound-components/01.problem.context/README.mdx
@@ -1,7 +1,9 @@
# Compound Components
-In this exercise we're going to make `` the parent of a few compound
-components:
+
+
+๐จโ๐ผ In this exercise we're going to make `` the parent of a few
+compound components:
- `` renders children when the `on` state is `true`
- `` renders children when the `on` state is `false`
@@ -19,7 +21,7 @@ mechanisms for updating that state (`toggle`) that are being shared between the
components.
So in this exercise, we'll solve that problem by using the ๐
-[React Context API](https://reactjs.org/docs/hooks-reference.html#use)!
+[React Context API](https://react.dev/reference/react/use#reading-context-with-use)!
What we want to do in this exercise is allow users of our component to render
something when the toggle button is on and to render something else when that
@@ -28,7 +30,7 @@ controlling whether it's shown or not.
Your job will be to make a `ToggleContext` which will be used to implicitly
share the state between these components. The `Toggle` component will render the
-`ToggleContext.Provider` and the other compound components will access that
+`ToggleContext` and the other compound components will access that
implicit state via `use(ToggleContext)`.
๐ฆบ TypeScript might not like your `use` call depending on how you set up your
diff --git a/exercises/03.compound-components/01.problem/app.tsx b/exercises/03.compound-components/01.problem.context/app.tsx
similarity index 100%
rename from exercises/03.compound-components/01.problem/app.tsx
rename to exercises/03.compound-components/01.problem.context/app.tsx
diff --git a/exercises/03.compound-components/01.problem/index.css b/exercises/03.compound-components/01.problem.context/index.css
similarity index 100%
rename from exercises/03.compound-components/01.problem/index.css
rename to exercises/03.compound-components/01.problem.context/index.css
diff --git a/exercises/03.compound-components/01.problem/index.tsx b/exercises/03.compound-components/01.problem.context/index.tsx
similarity index 100%
rename from exercises/03.compound-components/01.problem/index.tsx
rename to exercises/03.compound-components/01.problem.context/index.tsx
diff --git a/exercises/03.compound-components/01.problem/toggle.tsx b/exercises/03.compound-components/01.problem.context/toggle.tsx
similarity index 70%
rename from exercises/03.compound-components/01.problem/toggle.tsx
rename to exercises/03.compound-components/01.problem.context/toggle.tsx
index 3ce5a32a..308443a3 100644
--- a/exercises/03.compound-components/01.problem/toggle.tsx
+++ b/exercises/03.compound-components/01.problem.context/toggle.tsx
@@ -2,16 +2,16 @@ import { useState } from 'react'
import { Switch } from '#shared/switch.tsx'
// ๐จ create your ToggleContext context here
-// ๐ https://reactjs.org/docs/context.html#reactcreatecontext
-// ๐ฐ the default value should be `undefined`
+// ๐ https://react.dev/reference/react/createContext
+// ๐ฐ the default value should be `null`
// ๐ฆบ the typing for the context value should be `{on: boolean; toggle: () => void}`
-// but because we must initialize it to `undefined`, you need to union that with `undefined`
+// but because we must initialize it to `null`, you need to union that with `null`
export function Toggle({ children }: { children: React.ReactNode }) {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
- // ๐ฃ remove this and instead return where
+ // ๐ฃ remove this and instead return where
// the value is an object that has `on` and `toggle` on it. Render children
// within the provider.
return <>TODO...>
@@ -20,7 +20,7 @@ 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
// use(ToggleContext)
- // ๐ https://reactjs.org/docs/hooks-reference.html#use
+ // ๐ https://react.dev/reference/react/use#reading-context-with-use
const on = false
return <>{on ? children : null}>
}
@@ -31,7 +31,10 @@ export function ToggleOff({ children }: { children: React.ReactNode }) {
return <>{on ? null : children}>
}
-export function ToggleButton(props: React.ComponentProps) {
+type ToggleButtonProps = Omit, 'on'> & {
+ on?: boolean
+}
+export function ToggleButton(props: ToggleButtonProps) {
// ๐จ get `on` and `toggle` from the ToggleContext with `use`
const on = false
const toggle = () => {}
diff --git a/exercises/03.compound-components/01.solution.context/README.mdx b/exercises/03.compound-components/01.solution.context/README.mdx
new file mode 100644
index 00000000..8886fb4f
--- /dev/null
+++ b/exercises/03.compound-components/01.solution.context/README.mdx
@@ -0,0 +1,8 @@
+# Compound Components
+
+
+
+๐จโ๐ผ This is an extremely powerful feature that gives us nice and declarative APIs
+for our reusable components. However, it's possible people could be using it
+wrong so let's explore a way to help people avoid using our API incorrectly
+next.
diff --git a/exercises/03.compound-components/01.solution/app.tsx b/exercises/03.compound-components/01.solution.context/app.tsx
similarity index 100%
rename from exercises/03.compound-components/01.solution/app.tsx
rename to exercises/03.compound-components/01.solution.context/app.tsx
diff --git a/exercises/03.compound-components/01.solution/index.css b/exercises/03.compound-components/01.solution.context/index.css
similarity index 100%
rename from exercises/03.compound-components/01.solution/index.css
rename to exercises/03.compound-components/01.solution.context/index.css
diff --git a/exercises/03.compound-components/01.solution/index.tsx b/exercises/03.compound-components/01.solution.context/index.tsx
similarity index 100%
rename from exercises/03.compound-components/01.solution/index.tsx
rename to exercises/03.compound-components/01.solution.context/index.tsx
diff --git a/exercises/03.compound-components/01.solution/toggle.test.tsx b/exercises/03.compound-components/01.solution.context/toggle.test.tsx
similarity index 100%
rename from exercises/03.compound-components/01.solution/toggle.test.tsx
rename to exercises/03.compound-components/01.solution.context/toggle.test.tsx
diff --git a/exercises/03.compound-components/01.solution/toggle.tsx b/exercises/03.compound-components/01.solution.context/toggle.tsx
similarity index 68%
rename from exercises/03.compound-components/01.solution/toggle.tsx
rename to exercises/03.compound-components/01.solution.context/toggle.tsx
index c0cd9f87..978a30ee 100644
--- a/exercises/03.compound-components/01.solution/toggle.tsx
+++ b/exercises/03.compound-components/01.solution.context/toggle.tsx
@@ -2,18 +2,13 @@ import { createContext, use, useState } from 'react'
import { Switch } from '#shared/switch.tsx'
type ToggleValue = { on: boolean; toggle: () => void }
-const ToggleContext = createContext(undefined)
-ToggleContext.displayName = 'ToggleContext'
+const ToggleContext = createContext(null)
export function Toggle({ children }: { children: React.ReactNode }) {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
- return (
-
- {children}
-
- )
+ return {children}
}
export function ToggleOn({ children }: { children: React.ReactNode }) {
@@ -26,9 +21,10 @@ export function ToggleOff({ children }: { children: React.ReactNode }) {
return <>{on ? null : children}>
}
-export function ToggleButton({
- ...props
-}: Omit, 'on'>) {
+type ToggleButtonProps = Omit, 'on'> & {
+ on?: boolean
+}
+export function ToggleButton({ ...props }: ToggleButtonProps) {
const { on, toggle } = use(ToggleContext)!
return
}
diff --git a/exercises/03.compound-components/01.solution/README.mdx b/exercises/03.compound-components/01.solution/README.mdx
deleted file mode 100644
index db376826..00000000
--- a/exercises/03.compound-components/01.solution/README.mdx
+++ /dev/null
@@ -1,6 +0,0 @@
-# Compound Components
-
-This is an extremely powerful feature that gives us nice and declarative APIs
-for our reusable components. However, it's possible people could be using it
-wrong so let's explore a way to help people avoid using our API incorrectly
-next.
diff --git a/exercises/03.compound-components/02.problem.validation/README.mdx b/exercises/03.compound-components/02.problem.validation/README.mdx
index cd377274..d2227366 100644
--- a/exercises/03.compound-components/02.problem.validation/README.mdx
+++ b/exercises/03.compound-components/02.problem.validation/README.mdx
@@ -1,8 +1,10 @@
# Compound Components Validation
-Change to this (temporarily):
+
-```javascript
+๐จโ๐ผ Change to this (temporarily):
+
+```tsx
import { ToggleButton } from './toggle'
export const App = () =>
@@ -12,7 +14,7 @@ Why doesn't that work (it's not supposed to, but can you explain why)? Can you
figure out a way to give the developer a better error message that explains what
they're doing wrong and how to fix it?
-๐จ The tests will tell you the message for the error you must throw when the
+๐จ The tests will tell you in the message for the error you must throw when the
context is undefined.
๐ฆบ Additionally, this is where we can make TypeScript happier (TypeScript knew
diff --git a/exercises/03.compound-components/02.problem.validation/toggle.tsx b/exercises/03.compound-components/02.problem.validation/toggle.tsx
index c0cd9f87..1b86f4d8 100644
--- a/exercises/03.compound-components/02.problem.validation/toggle.tsx
+++ b/exercises/03.compound-components/02.problem.validation/toggle.tsx
@@ -2,33 +2,37 @@ import { createContext, use, useState } from 'react'
import { Switch } from '#shared/switch.tsx'
type ToggleValue = { on: boolean; toggle: () => void }
-const ToggleContext = createContext(undefined)
-ToggleContext.displayName = 'ToggleContext'
+const ToggleContext = createContext(null)
export function Toggle({ children }: { children: React.ReactNode }) {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
- return (
-
- {children}
-
- )
+ return {children}
}
+// ๐จ create a custom useToggle() hook here
+// create a new context variable and read ToggleContext with use
+// when no context is found, throw an error with a useful message
+// otherwise return the context
+
export function ToggleOn({ children }: { children: React.ReactNode }) {
+ // ๐จ instead reading context with use, we'll need to get that from useToggle()
const { on } = use(ToggleContext)!
return <>{on ? children : null}>
}
export function ToggleOff({ children }: { children: React.ReactNode }) {
+ // ๐จ instead reading context with use, we'll need to get that from useToggle()
const { on } = use(ToggleContext)!
return <>{on ? null : children}>
}
-export function ToggleButton({
- ...props
-}: Omit, 'on'>) {
+type ToggleButtonProps = Omit, 'on'> & {
+ on?: boolean
+}
+export function ToggleButton({ ...props }: ToggleButtonProps) {
+ // ๐จ instead reading context with use, we'll need to get that from useToggle()
const { on, toggle } = use(ToggleContext)!
return
}
diff --git a/exercises/03.compound-components/02.solution.validation/README.mdx b/exercises/03.compound-components/02.solution.validation/README.mdx
index f6df20ec..f24de7f3 100644
--- a/exercises/03.compound-components/02.solution.validation/README.mdx
+++ b/exercises/03.compound-components/02.solution.validation/README.mdx
@@ -1,6 +1,8 @@
# Compound Components Validation
-Runtime validation isn't the best (it would be better if we could enforce this
-statically via TypeScript), but unfortunately it's the best we can do with the
-composition model offered by React. That said, we it's unlikely people will mess
-this up now that we have this runtime validation in place.
+
+
+๐จโ๐ผ Runtime validation isn't the best (it would be better if we could enforce
+this statically via TypeScript), but unfortunately it's the best we can do with
+the composition model offered by React. That said, it's unlikely people will
+mess this up now that we have this runtime validation in place.
diff --git a/exercises/03.compound-components/02.solution.validation/toggle.tsx b/exercises/03.compound-components/02.solution.validation/toggle.tsx
index 2d337769..3c2db466 100644
--- a/exercises/03.compound-components/02.solution.validation/toggle.tsx
+++ b/exercises/03.compound-components/02.solution.validation/toggle.tsx
@@ -2,23 +2,18 @@ import { createContext, use, useState } from 'react'
import { Switch } from '#shared/switch.tsx'
type ToggleValue = { on: boolean; toggle: () => void }
-const ToggleContext = createContext(undefined)
-ToggleContext.displayName = 'ToggleContext'
+const ToggleContext = createContext(null)
export function Toggle({ children }: { children: React.ReactNode }) {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
- return (
-
- {children}
-
- )
+ return {children}
}
function useToggle() {
const context = use(ToggleContext)
- if (context === undefined) {
+ if (!context) {
throw new Error(
'Cannot find ToggleContext. All Toggle components must be rendered within ',
)
@@ -36,9 +31,10 @@ export function ToggleOff({ children }: { children: React.ReactNode }) {
return <>{on ? null : children}>
}
-export function ToggleButton({
- ...props
-}: Omit, 'on'>) {
+type ToggleButtonProps = Omit, 'on'> & {
+ on?: boolean
+}
+export function ToggleButton({ ...props }: ToggleButtonProps) {
const { on, toggle } = useToggle()
return
}
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 ca335c78..14c69b21 100644
--- a/exercises/03.compound-components/02.solution.validation/validation.test.tsx
+++ b/exercises/03.compound-components/02.solution.validation/validation.test.tsx
@@ -1,4 +1,4 @@
-import { expect, testStep } from '@kentcdodds/workshop-utils/test'
+import { expect, testStep } from '@epic-web/workshop-utils/test'
import { render } from '@testing-library/react'
import { ToggleButton, ToggleOff, ToggleOn } from './toggle.tsx'
diff --git a/exercises/03.compound-components/FINISHED.mdx b/exercises/03.compound-components/FINISHED.mdx
new file mode 100644
index 00000000..3b4bd64b
--- /dev/null
+++ b/exercises/03.compound-components/FINISHED.mdx
@@ -0,0 +1,8 @@
+# Compound Components
+
+
+
+๐จโ๐ผ Great work! You now know how to create one of the best composition APIs for
+UI component libraries. The vast majority of component libraries employ this
+pattern and even if you don't build your own, you'll be much better able to use
+these libraries because you understand how they work. Good job!
diff --git a/exercises/03.compound-components/README.mdx b/exercises/03.compound-components/README.mdx
index c056538d..bb09c382 100644
--- a/exercises/03.compound-components/README.mdx
+++ b/exercises/03.compound-components/README.mdx
@@ -1,8 +1,12 @@
# Compound Components
-**One liner:** The Compound Components Pattern enables you to provide a set of
-components that implicitly share state for a simple yet powerful declarative API
-for reusable components.
+
+
+
+ **One liner:** The Compound Components Pattern enables you to provide a set of
+ components that implicitly share state for a simple yet powerful declarative
+ API for reusable components.
+
Compound components are components that work together to form a complete UI. The
classic example of this is `