-
Notifications
You must be signed in to change notification settings - Fork 14
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
2e1a649
commit 7376aa5
Showing
22 changed files
with
277 additions
and
6 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,4 +1,4 @@ | ||
# Initial State | ||
|
||
👨💼 Great work! Now at least we're not just throwing an error. But let's handle | ||
the state update... | ||
the state update next. |
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 |
---|---|---|
@@ -1 +1,11 @@ | ||
# Update State | ||
|
||
👨💼 Alright, right now when you click the button, nothing happens. Let's get it | ||
to update the state. | ||
|
||
Update the `setState` function to assign the given state to the new state. | ||
|
||
<callout-warning> | ||
When you do this, it'll not actually update the number in the button either. | ||
We'll get to that soon! | ||
</callout-warning> |
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 |
---|---|---|
@@ -1 +1,4 @@ | ||
# Update State | ||
|
||
👨💼 So we're updating the state value, but it's not actually updating the number | ||
in the button? What gives?! |
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 |
---|---|---|
@@ -1 +1,30 @@ | ||
# Re-render | ||
|
||
👨💼 Ok, so we're initializing our state properly and we're updating the state | ||
properly as well. The problem is we're not updating the UI when the state gets | ||
updated. Remember, we're not React. We need to tell React when the state has | ||
changed so it will render our component again. | ||
|
||
Because we're not React, the way we will do this is by simply calling `render` | ||
our our root again. Remember what the bottom of our file looks like? | ||
|
||
```tsx lines=4 | ||
const rootEl = document.createElement('div') | ||
document.body.append(rootEl) | ||
const appRoot = createRoot(rootEl) | ||
appRoot.render(<Counter />) | ||
``` | ||
|
||
That last line there is where we render the component. So we just need to call | ||
that any time we want the component updated! | ||
|
||
So in this exercise, wrap that bit in a function and call it once for the | ||
initial render and once in the `setState` function. | ||
|
||
Feel free to toss in a `console.log` in the component to make sure it's | ||
re-rendering. | ||
|
||
<callout-warning> | ||
When you're finished with this, the UI will **still** not work like you | ||
expect. I promise we'll get to that very soon! | ||
</callout-warning> |
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 |
---|---|---|
@@ -1 +1,5 @@ | ||
# Re-render | ||
|
||
👨💼 Great work! Now we're not only updating the state, but we're also triggering | ||
a re-render so the UI can be updated. Unfortunately that seems to not be working | ||
either? Let's figure out why. |
25 changes: 25 additions & 0 deletions
25
exercises/01.use-state/04.problem.preserve-state/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 |
---|---|---|
@@ -1 +1,26 @@ | ||
# Preserve State | ||
|
||
👨💼 Alright, so there are actually two problems here. First, when the user clicks | ||
on the button, we update the `state` variable inside the `useState` closure, but | ||
that variable is not accessible by our component. Our component has its own | ||
variable called `count` which is not being updated | ||
|
||
<callout-info> | ||
Just because two variables point to the same object in memory (or in our case, | ||
the same number) doesn't mean they stay in sync when one is reassigned. That's | ||
just how JavaScript works. | ||
</callout-info> | ||
|
||
The second problem we have is when our component is called, it calls `useState` | ||
and that creates a brand new `state` variable that's assigned to the | ||
`initialState` variable again. | ||
|
||
So we need a way to preserve the state between renders. We can do that by | ||
pulling the `state` and `setState` variables outside the `useState` hook and | ||
simply assigning them on the initial render of the component. | ||
|
||
Give that a try! | ||
|
||
<callout-success> | ||
The button will finally work on this one, I promise! | ||
</callout-success> |
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 |
---|---|---|
@@ -1 +1,4 @@ | ||
# Preserve State | ||
|
||
👨💼 Great work! Our UI is working properly! By preserving our state we're able to | ||
make changes to it and render again whenever that value 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 |
---|---|---|
@@ -1 +1,7 @@ | ||
# useState | ||
|
||
👨💼 This is a great time for you to take a break and reflect on your learnings so | ||
far. Take a moment and then when you're ready, we'll see you in the next | ||
exercise. | ||
|
||
There's still plenty to do! |
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 |
---|---|---|
@@ -1 +1,31 @@ | ||
# Render Phase | ||
|
||
🧝♂️ Hi! I made a change to the code a bit. Now we're rendering two buttons, the | ||
count button is still there, but now we're also a button for disabling the count | ||
button. I needed to add another `useState` for that, but it's not working. You | ||
can <PrevDiffLink>check my work</PrevDiffLink> if you'd like. Can you get it | ||
working? Thanks! | ||
|
||
👨💼 Thanks for adding those buttons Kellie! | ||
|
||
Ok, so what we need you to do is fix the problem. If you add a | ||
`console.log({ count, enabled })` to the component, you'll get | ||
`{ count: 0, enabled: 0 }`. This is because the first time the `useState` is | ||
called initializes the state and the second time it's called, it just references | ||
the first one. | ||
|
||
So to start off fixing this issue, you're going to need to formalize how we | ||
determine whether state gets initialized or referenced. | ||
|
||
Really, `useState` can be called in two scenarios: | ||
|
||
- Initialization | ||
- Updates | ||
|
||
So we're going to keep track of how this is called with a `phase` variable. The | ||
emoji will guide you in the right direction. Good luck! | ||
|
||
<callout-warning> | ||
Note it's not going to quite work when you're finished with this step, but | ||
it'll work soon! | ||
</callout-warning> |
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 |
---|---|---|
@@ -1 +1,8 @@ | ||
# Render Phase | ||
|
||
👨💼 Great! It's not quite working yet, now if we add | ||
`console.log({ count, enabled })` to the component, we'll get | ||
`{ count: 0, enabled: true }` like you'd expect, but when you click the counter | ||
button we get `{ count: 1, enabled: 1 }` 😅. And if you click the disable button | ||
you get `{ count: false, enabled: false }`. What the heck is going on!? Let's | ||
find out. |
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 |
---|---|---|
@@ -1 +1,18 @@ | ||
# Hook ID | ||
|
||
👨💼 Based on what's happening now, I think we're not isolating the `state` | ||
between the two hooks. We need to uniquely identify each hook and store their | ||
state separately. | ||
|
||
We know that the hooks are called in the same order every time, so we could keep | ||
a call index (we'll call it the `hookIndex`) and increment it every time | ||
`useState` is called. That way, we could assign the first hook an ID of `0` and | ||
the second hook an ID of `1`. | ||
|
||
Then we store the `state` and `setState` in an array with their ID as the | ||
key. | ||
|
||
Then, whenever we render we just reset the `hookIndex` to `0` and we'll be | ||
golden! | ||
|
||
<callout-success>It'll work this time, I promise!</callout-success> |
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 |
---|---|---|
@@ -1 +1,5 @@ | ||
# Hook ID | ||
|
||
👨💼 Hey, that works! And now you understand why it's important to avoid | ||
conditionally calling hooks or call them in loops. Their call order is their | ||
only uniquely identifying trait! |
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 |
---|---|---|
@@ -1 +1,5 @@ | ||
# Multiple Hooks | ||
|
||
👨💼 Whew, now we've got multiple `useState` hooks in a single component working! | ||
It's a good time for a break. Write down what you learned, then we'll see you | ||
back soon! |
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 |
---|---|---|
@@ -1,10 +1,36 @@ | ||
# Multiple Hooks | ||
|
||
Often components require more than a single hook. In fact, often they'll use | ||
two or more of the `useState` hook. If we tried that with our implementation | ||
right now things wouldn't work so well and I think you can imagine why. | ||
|
||
The tricky part about this is when you have more than one hook, you need to be | ||
able to track their values over the lifetime of the component relative to each | ||
other and that component. What makes this difficult though is that there's no | ||
uniquely identifying information about the hooks: | ||
|
||
```tsx | ||
const [count1, setCount1] = useState(0) | ||
const [count2, setCount2] = useState(0) | ||
``` | ||
|
||
Having two elements of state like this in a component is perfectly legitimate, | ||
but our current implementation wouldn't work for that at all because the state | ||
would be shared between the two hooks. | ||
|
||
So how do we get around that? | ||
|
||
Well, it's not entirely true to say that there's no uniquely identifying | ||
information about the hooks.... There actually is something unique about these | ||
function calls and that is the order in which they are called! | ||
|
||
If we can assume that they'll always be called in the same order, then we can | ||
assign the first one an ID of `0` and the second one an ID of `1`. Then we can | ||
use that ID to track the state of the hooks! | ||
|
||
Something you will hopefully gather from this exercise is an understanding of | ||
why the ["rules of hooks"](https://react.dev/reference/rules/rules-of-hooks) is | ||
a thing. The rules are: | ||
|
||
- Only call Hooks at the top level | ||
- Only call Hooks from React functions | ||
a thing. Specifically the rule that hooks must be called at the top level (and | ||
not conditionally). | ||
|
||
As we work to implement th | ||
So, let's get into it! |
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 |
---|---|---|
@@ -1 +1,25 @@ | ||
# Callback | ||
|
||
🧝♂️ I've added a `useEffect`, but it's not supported yet so the app's busted: | ||
|
||
```tsx | ||
useEffect(() => { | ||
console.log('consider yourself effective!') | ||
}) | ||
``` | ||
|
||
You can <PrevDiffLink>check my work</PrevDiffLink> if you'd like. Can you fix | ||
it? Thanks! | ||
|
||
👨💼 Sure thing Kellie, thanks! | ||
|
||
Ok, so what we need to do here is going to feel a little familiar. We'll want to | ||
create an `effects` array that stores all the callbacks. Then our `useEffect` | ||
hook implementation will actually be pretty darn simple: get the `ID` for our | ||
hook, add the callback to the `effects` array. We don't even have to return | ||
anything. | ||
|
||
The tricky bit will be to make the `appRoot.render` call synchronous with | ||
`flushSync` so we can iterate through all the effects to call the callback. | ||
|
||
I think you can do it. Let's go! |
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 |
---|---|---|
@@ -1 +1,3 @@ | ||
# Callback | ||
|
||
👨💼 Great job! Now, can you handle the dependency array? |
23 changes: 23 additions & 0 deletions
23
exercises/03.use-effect/02.problem.dependencies/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 |
---|---|---|
@@ -1 +1,24 @@ | ||
# Dependencies | ||
|
||
🧝♂️ I've updated the `useEffect`: | ||
|
||
```tsx | ||
useEffect(() => { | ||
if (enabled) { | ||
console.log('consider yourself effective!') | ||
} else { | ||
console.log('consider yourself ineffective!') | ||
} | ||
}, [enabled]) | ||
``` | ||
|
||
You can <PrevDiffLink>check my work</PrevDiffLink> if you'd like. The app's not | ||
technically broken, but the logs should only happen when toggling enabled and | ||
right now we're getting logs when clicking the counter as well. | ||
|
||
👨💼 We can handle this! So you'll need to also keep track of the deps array and | ||
even the previous value of the deps array. Then just add a little logic to | ||
determine whether to call the effect callback based on whether any of the | ||
dependencies changed. | ||
|
||
You can do this. Let's go! |
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 |
---|---|---|
@@ -1 +1,3 @@ | ||
# Dependencies | ||
|
||
👨💼 Great work! Now the `useEffect` dependencies work. Well done 👏 |
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 |
---|---|---|
@@ -1 +1,8 @@ | ||
# useEffect | ||
|
||
👨💼 That's as far as we're going to take `useEffect`. Unfortunately there's just | ||
not a good way to handle the cleanup function since we don't have a way to track | ||
when a component gets added and removed from the page because the API React | ||
offers us for that is `useEffect` 😅 | ||
|
||
But hopefully you learned something valuable here! Write down what you learned! |
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 |
---|---|---|
@@ -1 +1,23 @@ | ||
# useEffect | ||
|
||
The `useEffect` hook has a simple API: | ||
|
||
```tsx | ||
useEffect(callback, [dep1, dep2]) | ||
``` | ||
|
||
The dependencies are optional, the callback can return a cleanup function. On | ||
re-renders that change the dependencies, the callback will be called again ( | ||
after first calling the previous cleanup if one was given). If no dependency | ||
array is provided, the callback will be called on every render. That's about it. | ||
|
||
We can follow the same pattern with storing the callback and dependencies as we | ||
did with `useState` before. And we can call the callbacks in the `render` | ||
function we have. Should be pretty simple! | ||
|
||
However, because we're not React, we don't actually know when the component has | ||
finished rendering. So we're going to use React's | ||
[`flushSync`](https://react.dev/reference/react-dom/flushSync) API to force the | ||
render to happen synchronously so that we can get the callbacks to run. | ||
|
||
So let's get into it! |
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 |
---|---|---|
@@ -1,3 +1,13 @@ | ||
# Build React Hooks 🪝 | ||
|
||
Hooray! You're all done! 👏👏 | ||
|
||
Of course there's much more we can do for our implementations to make them more | ||
like the actual built in version of these hooks, but I think we've gone far | ||
enough for you to understand how hooks work behind the scenes a bit. | ||
|
||
If you want to explore the actual implementation of hooks in the source code, | ||
[start here](https://github.com/facebook/react/blob/e02baf6c92833a0d45a77fb2e741676f393c24f7/packages/react-reconciler/src/ReactFiberHooks.js#L3837-L3964). | ||
It's pretty interesting (and certainly more complicated), but it's not entirely | ||
dissimilar to what we've implemented. Hopefully this helps you understand how | ||
React hooks work under the hood! |
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 |
---|---|---|
@@ -1,5 +1,18 @@ | ||
# Build React Hooks 🪝 | ||
|
||
👨💼 Hello, my name is Peter the Product Manager. I'm here to help you get | ||
oriented and to give you your assignments for the workshop! | ||
|
||
We're going to build our own implementation of React hooks so you get a deeper | ||
understanding of how they work behind the scenes. We'll be building `useState` | ||
and `useEffect`. | ||
|
||
<callout-warning> | ||
This will of course not be as rigerous as the official implementation. | ||
</callout-warning> | ||
|
||
You're gonna rock with this! Let's get going! | ||
|
||
🦺 One note, we're going to be doing some fun hackery around here so we'll be | ||
abusing TypeScript a bit. Feel free to throw around `any` and | ||
`// @ts-expect-error` here and there. |