-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9a6aeb3
commit 7b7eb57
Showing
63 changed files
with
407 additions
and
0 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
exercises/06.state-initializers/01.problem.initial/README.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Initialize Toggle | ||
|
||
👨💼 Our toggle component should be able to be customizable for the initial state | ||
and reset to the initial state. | ||
|
||
🧝♂️ I've updated the toggle component to use a reducer instead of `useState`. If | ||
you'd like to back up, and do that yourself in the playground, by my guest. Or | ||
you can <PrevDiffLink>check my work</PrevDiffLink> instead. | ||
|
||
👨💼 Please add a case in our reducer for the `reset` logic, and add an option to | ||
our `useToggle` hook for setting the initialOn state. |
16 changes: 16 additions & 0 deletions
16
exercises/06.state-initializers/01.problem.initial/app.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Switch } from '#shared/switch.tsx' | ||
import { useToggle } from './toggle.tsx' | ||
|
||
export function App() { | ||
// 🐨 add an initialOn option (set it to true) and get the reset callback from useToggle | ||
const { on, getTogglerProps } = useToggle() | ||
// 💣 delete this reset callback in favor of what you get from useToggle | ||
const reset = () => {} | ||
return ( | ||
<div> | ||
<Switch {...getTogglerProps({ on })} /> | ||
<hr /> | ||
<button onClick={reset}>Reset</button> | ||
</div> | ||
) | ||
} |
File renamed without changes.
File renamed without changes.
55 changes: 55 additions & 0 deletions
55
exercises/06.state-initializers/01.problem.initial/toggle.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { useReducer } from 'react' | ||
|
||
function callAll<Args extends Array<unknown>>( | ||
...fns: Array<((...args: Args) => unknown) | undefined> | ||
) { | ||
return (...args: Args) => fns.forEach(fn => fn?.(...args)) | ||
} | ||
|
||
type ToggleState = { on: boolean } | ||
type ToggleAction = { type: 'toggle' } | ||
// 🦺 add an action type for reset: | ||
// 💰 | { type: 'reset'; initialState: ToggleState } | ||
|
||
function toggleReducer(state: ToggleState, action: ToggleAction) { | ||
switch (action.type) { | ||
case 'toggle': { | ||
return { on: !state.on } | ||
} | ||
// 🐨 add a case for 'reset' that simply returns the "initialState" | ||
// which you can get from the action. | ||
} | ||
} | ||
|
||
// 🐨 We'll need to add an option for `initialOn` here (default to false) | ||
export function useToggle() { | ||
// 🐨 update the initialState object to use the initialOn option | ||
const initialState = { on: false } | ||
const [state, dispatch] = useReducer(toggleReducer, initialState) | ||
const { on } = state | ||
|
||
const toggle = () => dispatch({ type: 'toggle' }) | ||
|
||
// 🐨 add a reset function here which dispatches a 'reset' type with your | ||
// initialState object and calls `onReset` with the initialState.on value | ||
|
||
function getTogglerProps<Props>({ | ||
onClick, | ||
...props | ||
}: { | ||
onClick?: React.ComponentProps<'button'>['onClick'] | ||
} & Props) { | ||
return { | ||
'aria-checked': on, | ||
onClick: callAll(onClick, toggle), | ||
...props, | ||
} | ||
} | ||
|
||
return { | ||
on, | ||
toggle, | ||
// 🐨 add your reset function here. | ||
getTogglerProps, | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
exercises/06.state-initializers/01.solution.initial/README.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Initialize Toggle | ||
|
||
👨💼 Great job! Now we can initilize and reset the toggle component! But we've | ||
discovered a bug. Let's look at that next. |
13 changes: 13 additions & 0 deletions
13
exercises/06.state-initializers/01.solution.initial/app.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Switch } from '#shared/switch.tsx' | ||
import { useToggle } from './toggle.tsx' | ||
|
||
export function App() { | ||
const { on, getTogglerProps, reset } = useToggle({ initialOn: true }) | ||
return ( | ||
<div> | ||
<Switch {...getTogglerProps({ on })} /> | ||
<hr /> | ||
<button onClick={reset}>Reset</button> | ||
</div> | ||
) | ||
} |
File renamed without changes.
File renamed without changes.
52 changes: 52 additions & 0 deletions
52
exercises/06.state-initializers/01.solution.initial/toggle.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { useReducer } from 'react' | ||
|
||
function callAll<Args extends Array<unknown>>( | ||
...fns: Array<((...args: Args) => unknown) | undefined> | ||
) { | ||
return (...args: Args) => fns.forEach(fn => fn?.(...args)) | ||
} | ||
|
||
type ToggleState = { on: boolean } | ||
type ToggleAction = | ||
| { type: 'toggle' } | ||
| { type: 'reset'; initialState: ToggleState } | ||
|
||
function toggleReducer(state: ToggleState, action: ToggleAction) { | ||
switch (action.type) { | ||
case 'toggle': { | ||
return { on: !state.on } | ||
} | ||
case 'reset': { | ||
return action.initialState | ||
} | ||
} | ||
} | ||
|
||
export function useToggle({ initialOn = false } = {}) { | ||
const initialState = { on: initialOn } | ||
const [state, dispatch] = useReducer(toggleReducer, initialState) | ||
const { on } = state | ||
|
||
const toggle = () => dispatch({ type: 'toggle' }) | ||
const reset = () => dispatch({ type: 'reset', initialState }) | ||
|
||
function getTogglerProps<Props>({ | ||
onClick, | ||
...props | ||
}: { | ||
onClick?: React.ComponentProps<'button'>['onClick'] | ||
} & Props) { | ||
return { | ||
'aria-checked': on, | ||
onClick: callAll(onClick, toggle), | ||
...props, | ||
} | ||
} | ||
|
||
return { | ||
on, | ||
toggle, | ||
reset, | ||
getTogglerProps, | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
exercises/06.state-initializers/02.problem.stability/README.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Stability | ||
|
||
👨💼 We've noticed that if someone passes an `initialOn` that's based on state, | ||
then calling `reset` will sometimes change the state to the wrong value based on | ||
what the `initialOn` was set to at the time the component was initialized. | ||
|
||
This is confusing and we want to make certain to avoid it. | ||
|
||
🧝♂️ I've put together a simple example of this for you to experiment with. You'll | ||
notice we now have a button for toggling the `initialOn` state which we pass as | ||
an option to `useToggle`. So if you toggle the `initialOn` state and then click | ||
the reset button, you'll notice it resets to the current `initialOn` state, not | ||
the original one. | ||
|
||
👨💼 This is a little confusing for users of the `useToggle` hook, so please fix | ||
this issue! Thanks! |
18 changes: 18 additions & 0 deletions
18
exercises/06.state-initializers/02.problem.stability/app.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { useState } from 'react' | ||
import { Switch } from '#shared/switch.tsx' | ||
import { useToggle } from './toggle.tsx' | ||
|
||
export function App() { | ||
const [initialOn, setInitialOn] = useState(true) | ||
const { on, getTogglerProps, reset } = useToggle({ initialOn }) | ||
return ( | ||
<div> | ||
<button onClick={() => setInitialOn(o => !o)}> | ||
initialOn is: {initialOn ? 'true' : 'false'} | ||
</button> | ||
<Switch {...getTogglerProps({ on })} /> | ||
<hr /> | ||
<button onClick={reset}>Reset</button> | ||
</div> | ||
) | ||
} |
File renamed without changes.
File renamed without changes.
55 changes: 55 additions & 0 deletions
55
exercises/06.state-initializers/02.problem.stability/toggle.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { useReducer } from 'react' | ||
|
||
function callAll<Args extends Array<unknown>>( | ||
...fns: Array<((...args: Args) => unknown) | undefined> | ||
) { | ||
return (...args: Args) => fns.forEach(fn => fn?.(...args)) | ||
} | ||
|
||
type ToggleState = { on: boolean } | ||
type ToggleAction = | ||
| { type: 'toggle' } | ||
| { type: 'reset'; initialState: ToggleState } | ||
|
||
function toggleReducer(state: ToggleState, action: ToggleAction) { | ||
switch (action.type) { | ||
case 'toggle': { | ||
return { on: !state.on } | ||
} | ||
case 'reset': { | ||
return action.initialState | ||
} | ||
} | ||
} | ||
|
||
export function useToggle({ initialOn = false } = {}) { | ||
// 🐨 wrap this in a useRef | ||
const initialState = { on: initialOn } | ||
// 🐨 pass the ref-ed initial state into useReducer | ||
const [state, dispatch] = useReducer(toggleReducer, initialState) | ||
const { on } = state | ||
|
||
const toggle = () => dispatch({ type: 'toggle' }) | ||
// 🐨 make sure the ref-ed initial state gets passed here | ||
const reset = () => dispatch({ type: 'reset', initialState }) | ||
|
||
function getTogglerProps<Props>({ | ||
onClick, | ||
...props | ||
}: { | ||
onClick?: React.ComponentProps<'button'>['onClick'] | ||
} & Props) { | ||
return { | ||
'aria-checked': on, | ||
onClick: callAll(onClick, toggle), | ||
...props, | ||
} | ||
} | ||
|
||
return { | ||
on, | ||
toggle, | ||
reset, | ||
getTogglerProps, | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
exercises/06.state-initializers/02.solution.stability/README.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Stability | ||
|
||
👨💼 Great. Now we've got some stability with our state initializer. Well done! |
18 changes: 18 additions & 0 deletions
18
exercises/06.state-initializers/02.solution.stability/app.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { useState } from 'react' | ||
import { Switch } from '#shared/switch.tsx' | ||
import { useToggle } from './toggle.tsx' | ||
|
||
export function App() { | ||
const [initialOn, setInitialOn] = useState(true) | ||
const { on, getTogglerProps, reset } = useToggle({ initialOn }) | ||
return ( | ||
<div> | ||
<button onClick={() => setInitialOn(o => !o)}> | ||
initialOn is: {initialOn ? 'true' : 'false'} | ||
</button> | ||
<Switch {...getTogglerProps({ on })} /> | ||
<hr /> | ||
<button onClick={reset}>Reset</button> | ||
</div> | ||
) | ||
} |
File renamed without changes.
File renamed without changes.
52 changes: 52 additions & 0 deletions
52
exercises/06.state-initializers/02.solution.stability/toggle.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { useReducer, useRef } from 'react' | ||
|
||
function callAll<Args extends Array<unknown>>( | ||
...fns: Array<((...args: Args) => unknown) | undefined> | ||
) { | ||
return (...args: Args) => fns.forEach(fn => fn?.(...args)) | ||
} | ||
|
||
type ToggleState = { on: boolean } | ||
type ToggleAction = | ||
| { type: 'toggle' } | ||
| { type: 'reset'; initialState: ToggleState } | ||
|
||
function toggleReducer(state: ToggleState, action: ToggleAction) { | ||
switch (action.type) { | ||
case 'toggle': { | ||
return { on: !state.on } | ||
} | ||
case 'reset': { | ||
return action.initialState | ||
} | ||
} | ||
} | ||
|
||
export function useToggle({ initialOn = false } = {}) { | ||
const initialState = useRef({ on: initialOn }).current | ||
const [state, dispatch] = useReducer(toggleReducer, initialState) | ||
const { on } = state | ||
|
||
const toggle = () => dispatch({ type: 'toggle' }) | ||
const reset = () => dispatch({ type: 'reset', initialState }) | ||
|
||
function getTogglerProps<Props>({ | ||
onClick, | ||
...props | ||
}: { | ||
onClick?: React.ComponentProps<'button'>['onClick'] | ||
} & Props) { | ||
return { | ||
'aria-checked': on, | ||
onClick: callAll(onClick, toggle), | ||
...props, | ||
} | ||
} | ||
|
||
return { | ||
on, | ||
toggle, | ||
reset, | ||
getTogglerProps, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# State Initializer | ||
|
||
👨💼 Great job! You now know what to do to properly handle the initialization (and | ||
reset) of your state. | ||
|
||
🦉 Keep in mind that the `key` prop can also be used as a way to reset the state | ||
of a component, and it works pretty well. However it does require that | ||
everything is unmounted and remounted, which may not be what you want depending | ||
on the situation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# State Initializer | ||
|
||
<callout-success> | ||
**One liner:** The state initializer pattern is a way to initialize (and | ||
reset) the state of a component in a predictable way. | ||
</callout-success> | ||
|
||
This one is simple in concept: | ||
|
||
```tsx | ||
function useCounter() { | ||
const [count, setCount] = useState(0) | ||
const increment = () => setCount(c => c + 1) | ||
return { count, increment } | ||
} | ||
``` | ||
|
||
If I wanted to initialize the state of the count to a different value, I could | ||
do so by passing an argument to the `useCounter` function: | ||
|
||
```tsx | ||
function useCounter({ initialCount = 0 } = {}) { | ||
const [count, setCount] = useState(initialCount) | ||
const increment = () => setCount(c => c + 1) | ||
return { count, increment } | ||
} | ||
``` | ||
|
||
And often when you have a state initializer, you also have a state resetter: | ||
|
||
```tsx | ||
function useCounter({ initialCount = 0 } = {}) { | ||
const [count, setCount] = useState(initialCount) | ||
const increment = () => setCount(c => c + 1) | ||
const reset = () => setCount(initialCount) | ||
return { count, increment, reset } | ||
} | ||
``` | ||
|
||
But there's a catch. If you truly want to reset the component to its _initial_ | ||
state, then you need to make certain that any changes to the `initialCount` are | ||
ignored! | ||
|
||
You can do this, by using a `ref` which will keep the initial value constant | ||
across renders: | ||
|
||
```tsx | ||
function useCounter({ initialCount = 0 } = {}) { | ||
const initialCountRef = useRef(initialCount) | ||
const [count, setCount] = useState(initialCountRef.current) | ||
const increment = () => setCount(c => c + 1) | ||
const reset = () => setCount(initialCountRef.current) | ||
return { count, increment, reset } | ||
} | ||
``` | ||
|
||
And that's the crux of the state initializer pattern. |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@import '/switch.styles.css'; |
Oops, something went wrong.