Skip to content

Commit

Permalink
good progress
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Mar 8, 2024
1 parent 334c801 commit c6e32c8
Show file tree
Hide file tree
Showing 38 changed files with 644 additions and 34 deletions.
9 changes: 9 additions & 0 deletions exercises/01.use-reducer/01.problem.intro/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# useReducer

👨‍💼 Our users want a counter component and it's working fine, but Kellie 🧝‍♂️ has
said we can improve the implementation using `useReducer` (actually,
`useReducer` is absolutely overkill for a counter component like ours, but we'll
be using it to learn how to use it).

The emoji will guide you through this!

📜 [`useReducer` docs](https://react.dev/reference/react/useReducer).
2 changes: 2 additions & 0 deletions exercises/01.use-reducer/01.solution.intro/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# useReducer

👨‍💼 Great first step. Let's keep iterating.
12 changes: 12 additions & 0 deletions exercises/01.use-reducer/02.problem.previous-state/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
# Previous State

👨‍💼 We want to change things a bit to have this API:

```tsx
const [count, changeCount] = useReducer(countReducer, initialCount)
const increment = () => changeCount(step)
const decrement = () => changeCount(-step)
```

How would you need to change your reducer to make this work?

🦉 This step is just to show that you can pass anything as the action.
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Previous State

👨‍💼 Great, now you should have a good handle on how calling the `dispatch`
function with an argument sends that argument to your reducer. You have control!
34 changes: 34 additions & 0 deletions exercises/01.use-reducer/03.problem.object/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
# State Object

👨‍💼 Back in the day, we had `this.setState` from class components? We're going to
make the state updater (`dispatch` function) behave in a similar way by changing
our `state` to an object (`{count: 0}`) and then calling the state updater with
an object which merges with the current state.

So here's how I want things to look now:

```tsx
const [state, setState] = useReducer(countReducer, {
count: initialCount,
})
const { count } = state
const increment = () => setState({ count: count + step })
const decrement = () => setState({ count: count - step })
```

How would you need to change the reducer to make this work?

How would you make it support multiple state properties? For example:

```tsx
const [state, setState] = useReducer(countReducer, {
count: initialCount,
someOtherState: 'hello',
})
const { count } = state
const increment = () => setState({ count: count + step })
const decrement = () => setState({ count: count - step })
```

Calling `increment` or `decrement` in this case should only update the `count`
property and leave the `someOtherState` property alone. So the `setState`
function should merge the new state with the old state.
2 changes: 2 additions & 0 deletions exercises/01.use-reducer/03.solution.object/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# State Object

👨‍💼 Great! Hopefully you're starting to get the hang of this API!
16 changes: 16 additions & 0 deletions exercises/01.use-reducer/04.problem.function/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# Action Function

👨‍💼 `this.setState` from class components can also accept a function. So let's
add support for that with our simulated `setState` function. See if you can
figure out how to make your reducer support both the object as in the last step
as well as a function callback:

```tsx
const [state, setState] = useReducer(countReducer, {
count: initialCount,
})
const { count } = state
const increment = () =>
setState(currentState => ({ count: currentState.count + step }))
const decrement = () =>
setState(currentState => ({ count: currentState.count - step }))
```
2 changes: 2 additions & 0 deletions exercises/01.use-reducer/04.solution.function/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# Action Function

👨‍💼 Great work! You're doing awesome making this as dynamic as possible.
19 changes: 19 additions & 0 deletions exercises/01.use-reducer/05.problem.traditional/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
# Traditional Reducer

👨‍💼 Now it's time to get to the actual convention for reducers in React apps.

Update your reducer so I can do this:

```javascript
const [state, dispatch] = useReducer(countReducer, {
count: initialCount,
})
const { count } = state
const increment = () => dispatch({ type: 'INCREMENT', step })
const decrement = () => dispatch({ type: 'DECREMENT', step })
```

The key here is that the logic for updating the state is now in the reducer, and
the component is just dispatching actions. This actually gives us a bit of a
declarative API for updating state, which is nice. The component is just saying
what it wants to happen, and the reducer is the one that decides how to make it
happen.
3 changes: 3 additions & 0 deletions exercises/01.use-reducer/05.solution.traditional/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Traditional Reducer

👨‍💼 Great work! This is how we typically use the `useReducer` API conventionally
and it's kinda nice for complex state management.
6 changes: 6 additions & 0 deletions exercises/01.use-reducer/06.problem.tic-tac-toe/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
# Real World

👨‍💼 Let's try our hand at using `useReducer` for something a little more real.
We'll be refactoring the `useState` out of our tic-tac-toe game to use
`useReducer` instead.

Good luck!
3 changes: 3 additions & 0 deletions exercises/01.use-reducer/06.solution.tic-tac-toe/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Real World

👨‍💼 Great! The game functions the same, but the state changes are encapsulated in
the reducer which makes it easier to manage and test.
4 changes: 4 additions & 0 deletions exercises/01.use-reducer/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Advanced State Management

👨‍💼 Great work! You've now explored the `useReducer` API pretty well and you
should feel comfortable using it going forward.
9 changes: 7 additions & 2 deletions exercises/01.use-reducer/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ help you learn the difference between the convention and the actual API.
Here's an example of using `useReducer` to manage the value of a name in an
input.

```javascript
```tsx
function nameReducer(previousName, newName) {
return newName
}

const initialNameValue = 'Joe'

function NameInput() {
const [name, setName] = React.useReducer(nameReducer, initialNameValue)
const [name, setName] = useReducer(nameReducer, initialNameValue)
const handleChange = event => setName(event.target.value)
return (
<>
Expand All @@ -43,3 +43,8 @@ above) is called with two arguments:
1. the current state
2. whatever it is that the dispatch function (called `setName` above) is called
with. This is often called an "action."

📜 Here are two really helpful blog posts comparing `useState` and `useReducer`:

- [Should I useState or useReducer?](https://kentcdodds.com/blog/should-i-usestate-or-usereducer)
- [How to implement useState with useReducer](https://kentcdodds.com/blog/how-to-implement-usestate-with-usereducer)
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Optimize state updates

{/* prettier-ignore */}
<callout-info>
We're going to be working with the URL, so you'll want to pull this one up in
<a target="_blank" href="/app/playground">a separate tab</a>.
</callout-info>

👨‍💼 We're bringing back our search and card page. Now we are storing the entire
`URLSearchParams` in state (not just the `query` param) and we want to make sure
we don't rerender the page if the params are unchanged.
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Optimize state updates

👨‍💼 Great! It's nice to know that with a simple check we can avoid triggering an
unnecessary state update and improve performance a bit.
11 changes: 11 additions & 0 deletions exercises/02.state-optimization/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# State Optimization

👨‍💼 Great! Now you know how to prevent unnecessary state changes that trigger
rerenders. Good work.

🦉 Normally you don't want to add complexity to your code to improve performance
unless you have measured before/after to make certain that the change is actually
worth it. In this case, the performance improvement is likely to be negligible
unless you have a very large number of state updates happening in a short period
of time. But the added complexity is probably about as negligible as the
performance improvement, so either way it's a wash.
29 changes: 29 additions & 0 deletions exercises/03.custom-hooks/01.problem.function/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
# Hook Function

👨‍💼 We now want to create a reusable `useSearchParams` function which will handle
the search params for us generally so we can use that logic in other components.

<callout-info>
There's a much more complete version of this hook as a part of
[react-router](https://reactrouter.com/en/main/hooks/use-search-params) which
you'll likely want to use in a real application. This is just a simple
example.
</callout-info>

So your job is to take the logic from the `App` component that relates to the
search params and put it in a new function called `useSearchParams`, then you'll
use that function in the `App` component.

```tsx
const [searchParams, setSearchParams] = useSearchParams()
```

For the types of that tuple, you may find this article helpful:
[Wrapping React.useState with TypeScript](https://kentcdodds.com/blog/wrapping-react-use-state-with-type-script)

<callout-info class="aside">
There are actually not a lot of lines changed in this step of the exercise,
but we're going to be bringing in `useCallback` in the next step so keep
going!
</callout-info>

Good luck!
2 changes: 2 additions & 0 deletions exercises/03.custom-hooks/01.solution.function/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# Hook Function

👨‍💼 Great job! You've made a custom hook!
8 changes: 8 additions & 0 deletions exercises/03.custom-hooks/02.problem.callback/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# useCallback

👨‍💼 We only call the `setSearchParams` function inside event handlers, so we
don't have any problems, but we're making a reusable hook and we want to make
certain people don't have problems if they need to use it in a `useEffect` or
other hook that requires a dependency array.

So I want you to wrap our `setSearchParams` function in `useCallback` to memoize
it and avoid issues with the dependency array.
3 changes: 3 additions & 0 deletions exercises/03.custom-hooks/02.solution.callback/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# useCallback

👨‍💼 Great job! Now we have a pretty good reusable hook for anyone who wants to
control the search params!
10 changes: 10 additions & 0 deletions exercises/03.custom-hooks/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Custom Hooks

👨‍💼 Great job!

🦉 Remember,

> [Custom hooks are functions that use other hooks.](https://twitter.com/kentcdodds/status/1763633880349987151)
That's the only technical requirement. The `use` prefix is a useful convention,
but they're just functions.
Loading

0 comments on commit c6e32c8

Please sign in to comment.