+ )
+}
+
+export default AppWithUnmountCheckbox
diff --git a/src/exercise/02.js b/src/exercise/02.js
index 654f19dd..df70dbd2 100644
--- a/src/exercise/02.js
+++ b/src/exercise/02.js
@@ -10,20 +10,16 @@ import {
PokemonErrorBoundary,
} from '../pokemon'
-// 🐨 this is going to be our generic asyncReducer
-function pokemonInfoReducer(state, action) {
+function asyncReducer(state, action) {
switch (action.type) {
case 'pending': {
- // 🐨 replace "pokemon" with "data"
- return {status: 'pending', pokemon: null, error: null}
+ return {status: 'pending', data: null, error: null}
}
case 'resolved': {
- // 🐨 replace "pokemon" with "data" (in the action too!)
- return {status: 'resolved', pokemon: action.pokemon, error: null}
+ return {status: 'resolved', data: action.data, error: null}
}
case 'rejected': {
- // 🐨 replace "pokemon" with "data"
- return {status: 'rejected', pokemon: null, error: action.error}
+ return {status: 'rejected', data: null, error: action.error}
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
@@ -31,56 +27,42 @@ function pokemonInfoReducer(state, action) {
}
}
-function PokemonInfo({pokemonName}) {
- // 🐨 move all the code between the lines into a new useAsync function.
- // 💰 look below to see how the useAsync hook is supposed to be called
- // 💰 If you want some help, here's the function signature (or delete this
- // comment really quick if you don't want the spoiler)!
- // function useAsync(asyncCallback, initialState, dependencies) {/* code in here */}
-
- // -------------------------- start --------------------------
-
- const [state, dispatch] = React.useReducer(pokemonInfoReducer, {
- status: pokemonName ? 'pending' : 'idle',
- // 🐨 this will need to be "data" instead of "pokemon"
- pokemon: null,
+function useAsync(asyncCallback, initialState, dependencies) {
+ const [state, dispatch] = React.useReducer(asyncReducer, {
+ status: 'idle',
+ data: null,
error: null,
+ ...initialState,
})
React.useEffect(() => {
- // 💰 this first early-exit bit is a little tricky, so let me give you a hint:
- // const promise = asyncCallback()
- // if (!promise) {
- // return
- // }
- // then you can dispatch and handle the promise etc...
- if (!pokemonName) {
- return
- }
+ const promise = asyncCallback()
+ if (!promise) return
dispatch({type: 'pending'})
- fetchPokemon(pokemonName).then(
- pokemon => {
- dispatch({type: 'resolved', pokemon})
+ promise.then(
+ data => {
+ dispatch({type: 'resolved', data})
},
error => {
dispatch({type: 'rejected', error})
},
)
- // 🐨 you'll accept dependencies as an array and pass that here.
- // 🐨 because of limitations with ESLint, you'll need to ignore
- // the react-hooks/exhaustive-deps rule. We'll fix this in an extra credit.
- }, [pokemonName])
- // --------------------------- end ---------------------------
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, dependencies)
- // 🐨 here's how you'll use the new useAsync hook you're writing:
- // const state = useAsync(() => {
- // if (!pokemonName) {
- // return
- // }
- // return fetchPokemon(pokemonName)
- // }, {/* initial state */}, [pokemonName])
- // 🐨 this will change from "pokemon" to "data"
- const {pokemon, status, error} = state
+ return state
+}
+
+function PokemonInfo({pokemonName}) {
+ const state = useAsync(
+ () => {
+ if (!pokemonName) return
+ return fetchPokemon(pokemonName)
+ },
+ {},
+ [pokemonName],
+ )
+ const {data: pokemon, status, error} = state
switch (status) {
case 'idle':
diff --git a/src/exercise/02.md b/src/exercise/02.md
index 18f5b184..ea1c5bcd 100644
--- a/src/exercise/02.md
+++ b/src/exercise/02.md
@@ -386,4 +386,4 @@ those two situations.
## 🦉 Feedback
Fill out
-[the feedback form](https://ws.kcd.im/?ws=Advanced%20React%20Hooks%20%F0%9F%94%A5&e=02%3A%20useCallback%3A%20custom%20hooks&em=).
+[the feedback form](https://ws.kcd.im/?ws=Advanced%20React%20Hooks%20%F0%9F%94%A5&e=02%3A%20useCallback%3A%20custom%20hooks&em=elvinciqueira%40gmail.com).
diff --git a/src/exercise/03.extra-2.js b/src/exercise/03.extra-2.js
index 29d9cd56..6fb36bc9 100644
--- a/src/exercise/03.extra-2.js
+++ b/src/exercise/03.extra-2.js
@@ -15,14 +15,22 @@ import {
} from '../pokemon'
import {useAsync} from '../utils'
-// 🐨 Create a PokemonCacheContext
+const PokemonCacheContext = React.createContext()
-// 🐨 create a PokemonCacheProvider function
-// 🐨 useReducer with pokemonCacheReducer in your PokemonCacheProvider
-// 💰 you can grab the one that's in PokemonInfo
-// 🐨 return your context provider with the value assigned to what you get back from useReducer
-// 💰 value={[cache, dispatch]}
-// 💰 make sure you forward the props.children!
+function PokemonCacheProvider(props) {
+ const [cache, dispatch] = React.useReducer(pokemonCacheReducer, {})
+ return
+}
+
+function usePokemonCache() {
+ const context = React.useContext(PokemonCacheContext)
+ if (!context) {
+ throw new Error(
+ 'usePokemonCache must be used within a PokemonCacheProvider',
+ )
+ }
+ return context
+}
function pokemonCacheReducer(state, action) {
switch (action.type) {
@@ -35,12 +43,19 @@ function pokemonCacheReducer(state, action) {
}
}
-function PokemonInfo({pokemonName}) {
- // 💣 remove the useReducer here (or move it up to your PokemonCacheProvider)
- const [cache, dispatch] = React.useReducer(pokemonCacheReducer, {})
- // 🐨 get the cache and dispatch from useContext with PokemonCacheContext
+function PokemonInfo({pokemonName: externalPokemonName}) {
+ const [cache, dispatch] = usePokemonCache()
- const {data: pokemon, status, error, run, setData} = useAsync()
+ const pokemonName = externalPokemonName?.toLowerCase()
+ const {
+ data: pokemon,
+ status,
+ error,
+ run,
+ setData,
+ } = useAsync({
+ status: pokemonName ? 'pending' : 'idle',
+ })
React.useEffect(() => {
if (!pokemonName) {
@@ -55,7 +70,7 @@ function PokemonInfo({pokemonName}) {
}),
)
}
- }, [cache, pokemonName, run, setData])
+ }, [cache, dispatch, pokemonName, run, setData])
if (status === 'idle') {
return 'Submit a pokemon'
@@ -69,8 +84,7 @@ function PokemonInfo({pokemonName}) {
}
function PreviousPokemon({onSelect}) {
- // 🐨 get the cache from useContext with PokemonCacheContext
- const cache = {}
+ const [cache] = usePokemonCache()
return (
Previous Pokemon
@@ -91,19 +105,19 @@ function PreviousPokemon({onSelect}) {
}
function PokemonSection({onSelect, pokemonName}) {
- // 🐨 wrap this in the PokemonCacheProvider so the PreviousPokemon
- // and PokemonInfo components have access to that context.
return (
)
}
diff --git a/src/exercise/03.js b/src/exercise/03.js
index 4243e6b7..1c5df295 100644
--- a/src/exercise/03.js
+++ b/src/exercise/03.js
@@ -3,23 +3,29 @@
import * as React from 'react'
-// 🐨 create your CountContext here with React.createContext
+const CountContext = React.createContext()
-// 🐨 create a CountProvider component here that does this:
-// 🐨 get the count state and setCount updater with React.useState
-// 🐨 create a `value` array with count and setCount
-// 🐨 return your context provider with the value assigned to that array and forward all the other props
-// 💰 more specifically, we need the children prop forwarded to the context provider
+function CountProvider(props) {
+ const [count, setCount] = React.useState(0)
+ const value = [count, setCount]
+ return
+}
+
+function useCount() {
+ const context = React.useContext(CountContext)
+ if (!context) {
+ throw new Error('useCount must be used within a CountProvider')
+ }
+ return context
+}
function CountDisplay() {
- // 🐨 get the count from useContext with the CountContext
- const count = 0
+ const [count] = useCount()
return
{`The current count is ${count}`}
}
function Counter() {
- // 🐨 get the setCount from useContext with the CountContext
- const setCount = () => {}
+ const [, setCount] = useCount()
const increment = () => setCount(c => c + 1)
return
}
@@ -27,12 +33,10 @@ function Counter() {
function App() {
return (
- {/*
- 🐨 wrap these two components in the CountProvider so they can access
- the CountContext value
- */}
-
-
+
+
+
+
)
}
diff --git a/src/exercise/03.md b/src/exercise/03.md
index 05900185..56b7e54d 100644
--- a/src/exercise/03.md
+++ b/src/exercise/03.md
@@ -154,4 +154,4 @@ most has improved performance and maintainability characteristics.
## 🦉 Feedback
Fill out
-[the feedback form](https://ws.kcd.im/?ws=Advanced%20React%20Hooks%20%F0%9F%94%A5&e=03%3A%20useContext%3A%20simple%20Counter&em=).
+[the feedback form](https://ws.kcd.im/?ws=Advanced%20React%20Hooks%20%F0%9F%94%A5&e=03%3A%20useContext%3A%20simple%20Counter&em=elvinciqueira%40gmail.com).
diff --git a/src/exercise/04.js b/src/exercise/04.js
index 87e74795..7b0dfa19 100644
--- a/src/exercise/04.js
+++ b/src/exercise/04.js
@@ -5,8 +5,7 @@ import * as React from 'react'
function MessagesDisplay({messages}) {
const containerRef = React.useRef()
- // 🐨 replace useEffect with useLayoutEffect
- React.useEffect(() => {
+ React.useLayoutEffect(() => {
containerRef.current.scrollTop = containerRef.current.scrollHeight
})
@@ -31,9 +30,9 @@ function sleep(time = 0) {
function SlooooowSibling() {
// try this with useLayoutEffect as well to see
// how it impacts interactivity of the page before updates.
- React.useEffect(() => {
+ React.useLayoutEffect(() => {
// increase this number to see a more stark difference
- sleep(300)
+ sleep(1000)
})
return null
}
diff --git a/src/exercise/04.md b/src/exercise/04.md
index c9f22fca..ad381f4f 100644
--- a/src/exercise/04.md
+++ b/src/exercise/04.md
@@ -47,4 +47,4 @@ making observable changes to the DOM, then it should happen in
## 🦉 Feedback
Fill out
-[the feedback form](https://ws.kcd.im/?ws=Advanced%20React%20Hooks%20%F0%9F%94%A5&e=04%3A%20useLayoutEffect%3A%20auto-scrolling%20textarea&em=).
+[the feedback form](https://ws.kcd.im/?ws=Advanced%20React%20Hooks%20%F0%9F%94%A5&e=04%3A%20useLayoutEffect%3A%20auto-scrolling%20textarea&em=elvinciqueira%40gmail.com).
diff --git a/src/exercise/05.js b/src/exercise/05.js
index 05f7da2d..100791e4 100644
--- a/src/exercise/05.js
+++ b/src/exercise/05.js
@@ -4,23 +4,27 @@
import * as React from 'react'
// 🐨 wrap this in a React.forwardRef and accept `ref` as the second argument
-function MessagesDisplay({messages}) {
+const MessagesDisplay = React.forwardRef(function MessagesDisplay(
+ {messages},
+ ref,
+) {
const containerRef = React.useRef()
React.useLayoutEffect(() => {
scrollToBottom()
})
- // 💰 you're gonna want this as part of your imperative methods
- // function scrollToTop() {
- // containerRef.current.scrollTop = 0
- // }
+ React.useImperativeHandle(ref, () => ({
+ scrollToTop,
+ scrollToBottom,
+ }))
+
+ function scrollToTop() {
+ containerRef.current.scrollTop = 0
+ }
function scrollToBottom() {
containerRef.current.scrollTop = containerRef.current.scrollHeight
}
- // 🐨 call useImperativeHandle here with your ref and a callback function
- // that returns an object with scrollToTop and scrollToBottom
-
return (