Skip to content

Commit

Permalink
finish exercise 3 instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Mar 5, 2024
1 parent e2e614e commit 51a9881
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 5 deletions.
2 changes: 1 addition & 1 deletion exercises/02.side-effects/01.problem.effects/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ the URL search params without triggering a full-page refresh. So whenever the
user clicks "submit," we update the URL search params. The trouble is, when they
hit the back button, the search doesn't stay synchronoized with the URL (yet).

You can take a look at <DiffLink app1={-1} app2={0}>my changes</DiffLink> for
You can take a look at <DiffLink app1={-1}>my changes</DiffLink> for
details on what I did.

👨‍💼 Thanks Kellie. So what we you to do is make it so when the user hits the back
Expand Down
20 changes: 20 additions & 0 deletions exercises/03.lifting-state/01.problem.lift/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# Lift State

🧝‍♂️ I refactored the app a bit. In the process I kinda broke stuff and you'll be
required to fix it. Feel free to <DiffLink app1={-1}>check the diff</DiffLink>
to know what I've done. I pretty much just moved stuff into separate components
to get a little more organization in here.

<callout-info>
🦉 check out [When to break up a component into multiple
components](https://kentcdodds.com/blog/when-to-break-up-a-component-into-multiple-components).
</callout-info>

🧝‍♂️ Oh, also I added a new feature. You can now "like" specific posts by clicking
a little heart on the post. How nice! It's not persisted anywhere yet, so when
you refresh the page the likes all get reset, but it's a nice start!

👨‍💼 Great, now your job is to fix what's broken. You need to move not only the
state, but also we're going to have you move the `useEffect` that sets the query
as well.

Then you're going to need to pass props to the components as needed. Enjoy!
4 changes: 4 additions & 0 deletions exercises/03.lifting-state/01.solution.lift/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Lift State

👨‍💼 Great! It's working now!

Hmmm... What's that I hear? It's the sound of a new requirement coming in! 😆
11 changes: 11 additions & 0 deletions exercises/03.lifting-state/02.problem.lift-array/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
# Lift More State

👨‍💼 The users loved 🧝‍♂️ Kellie's like feature so much they want to have the blog
posts sorted by whether they're favorites.

🧝‍♂️ I've already created the sort function. Now you need to add the logic to sort
them based on whether they're favorited. And that means you need to lift some
state...

👨‍💼 Yep. We're going to need you to lift the favorited state from the cards to
the `MatchingPost` component. We'll also need to restructure it slightly as
well. Don't worry. The emoji will guide you. Good luck!
3 changes: 1 addition & 2 deletions exercises/03.lifting-state/02.problem.lift-array/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ function MatchingPosts({ query }: { query: string }) {
// 🐨 determine whether post a and b are included in favorites
const aFav = false // 💰 favorites.includes(a.id)
const bFav = false // 💰 favorites.includes(b.id)
if (aFav === bFav) return a.title.localeCompare(b.title)
return aFav ? -1 : 1
return aFav === bFav ? 0 : aFav ? -1 : 1
})
.map(post => (
<Card
Expand Down
2 changes: 2 additions & 0 deletions exercises/03.lifting-state/02.solution.lift-array/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# Lift More State

👨‍💼 Great job! Unfortunately... I have some bad news. Let's talk about that next.
17 changes: 15 additions & 2 deletions exercises/03.lifting-state/03.problem.colocate/README.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Colocate State

👨‍💼 Well, the users thought they wanted the articles sorted by whether they were
favorited... But after using the feature, they found it to be jarring and
confusing. So we're going to need you to remove that feature. Kellie already
removed the sorting for you, but she didn't have time to move the state back to
the `Card` component.

As a community we're pretty good at lifting state. It becomes natural over time.
One thing that we typically have trouble remembering to do is to push state back
down (or
In fact it's required to make the feature work. But as you notice here, the
functionality we want is already "working" without moving any of the state
around so it's easy to forget to improve the performance and maintainability of
our code by moving state back down (or
[colocate state](https://kentcdodds.com/blog/state-colocation-will-make-your-react-app-faster)).

So your job is to move the `favorited` state back to the `Card` component.

When you're finished, the functionality should be no different, but the code
should feel simpler.
3 changes: 3 additions & 0 deletions exercises/03.lifting-state/03.solution.colocate/README.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Colocate State

👨‍💼 Awesome! You now know how to colocate state! Try to do this as much as
possible in the future.
2 changes: 2 additions & 0 deletions exercises/03.lifting-state/FINISHED.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# Lifting State

👨‍💼 Hooray! You've learned how to move state around as needed like a pro!
143 changes: 143 additions & 0 deletions exercises/03.lifting-state/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,146 @@ which basically amounts to finding the lowest common parent shared between the
two components and placing the state management there, and then passing the
state and a mechanism for updating that state down into the components that need
it.

Let's look at a simple example.

```tsx
import { useState } from 'react'

function App() {
return (
<div>
<Counter />
</div>
)
}

function Counter() {
const [count, setCount] = useState(0)

return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
```

In this example, the `Counter` component has its own state. What if I wanted to
make a fancy `CountDisplay` component that was a sibling to the Counter:

```tsx
function App() {
return (
<div>
<Counter />
<CountDisplay />
</div>
)
}

function CountDisplay() {
return <div>Count: 0</div>
}
```

How can I get the `CountDisplay` to show the current count from the `Counter`?
The answer is to lift the state up to the `App` component and then pass the
state and a mechanism for updating that state down into the `Counter` and
`CountDisplay` components.

```tsx
function App() {
const [count, setCount] = useState(0)

return (
<div>
<Counter count={count} setCount={setCount} />
<CountDisplay count={count} />
</div>
)
}

function Counter({ count, setCount }) {
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}

function CountDisplay({ count }) {
return <div>Count: {count}</div>
}
```

Now the `App` component is managing the state and passing it down to the
`Counter` and `CountDisplay` components. This is a simple example, but the
principle is the same for more complex state management.

There are other ways to get the state around to components that need it. We'll
cover my preferred pattern of using composition in the Advanced React Patterns
workshop, but moving state up the tree is pretty common.

## Colocating state

One thing that's not as common as it should be in React is moving state the
other direction when changes are made. This is because you don't have to do it
for things to work, but it's better for performance and maintainability if you
do.

Let's take our example further by saying that we no longer need the
`CountDisplay` component to show the count. Instead, we want to show the count
in the button of the Counter. So we might do this:

```tsx
function App() {
const [count, setCount] = useState(0)

return (
<div>
<Counter count={count} setCount={setCount} />
</div>
)
}

function Counter({ count, setCount }) {
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment ({count})</button>
</div>
)
}
```

But now the `App` component is managing the state and passing it down to the
`Counter` component, but nothing but the `Counter` needs this state. So we can
move the state back down to the `Counter` component:

```tsx
function App() {
return (
<div>
<Counter />
</div>
)
}

function Counter() {
const [count, setCount] = useState(0)

return (
<div>
<button onClick={() => setCount(count + 1)}>Increment ({count})</button>
</div>
)
}
```

Novice React developers learn to lift state pretty early, but experienced React
developers know how to recognize when state can be "pushed down."

Let's do both in this exercise.

Read more about state colocation from
[State Colocation will make your React app faster](https://kentcdodds.com/blog/state-colocation-will-make-your-react-app-faster).

0 comments on commit 51a9881

Please sign in to comment.