-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
d6068e7
commit a77b46b
Showing
20 changed files
with
514 additions
and
67 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
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 @@ | ||
# Refs | ||
|
||
👨💼 Great job! Now our users have a fancy counter! |
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,32 @@ | ||
# Dependencies | ||
|
||
👨💼 Our users wanted to be able to control `vanilla-tilt` a bit. Some of them | ||
like the speed and glare to look different. So Kellie 🧝♂️ added a form that will | ||
allow them to control those values. The trouble is when the users change the | ||
values, the vanilla tilt element isn't updated with the new values. | ||
|
||
🦉 React needs to know when it needs to run your effect callback function again. | ||
We do this using the dependency array which is the second argument to | ||
`useEffect`. Whenever values in that array changes, React will call the returned | ||
cleanup function and then invoke the effect callback again. | ||
|
||
So far we've provided an empty dependency array which effectively tells React | ||
that the effect doesn't depend on any values from the component. This is why the | ||
effect is only run once when the component mounts and when values change it's | ||
not run again. | ||
|
||
By default, if you don't provide a second argument, `useEffect` runs after every | ||
render. While this is probably the right default for correctness, it's far from | ||
optimal in most cases. If you're not careful, it's easy to end up with infinite | ||
loops (imagine if you're calling `setState` in the effect which triggers another | ||
render and so on). | ||
|
||
👨💼 So what we need to do in this step is let React know that our effect callback | ||
depends on the `vanillaTiltOptions` the user is providing. Let's do that by | ||
passing the `vanillaTiltOptions` in the dependency array. | ||
|
||
<callout-warning class="aside"> | ||
You'll notice an issue when you've finished this step. If you click the button | ||
to increment the count, the tilt effect is reset! We'll fix this in the next | ||
step. | ||
</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
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 @@ | ||
# Dependencies | ||
|
||
👨💼 Great! Now our users can control the tilt effect and that makes them happy. | ||
But they're annoyed about something... |
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
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,30 @@ | ||
# Primitive Dependencies | ||
|
||
👨💼 Our users are annoyed. Whenever they click the incrementing button in the | ||
middle, the tilt effect is reset. You can reproduce this more easily by clicking | ||
one of the corners of the button. | ||
|
||
If you add a `console.log` to the `useEffect`, you'll notice that it runs even | ||
when the button is clicked, even if the actual options are unchanged. The reason | ||
is because the `options` object actually _did_ change! This is because the | ||
`options` object is a new object every time the component renders. This is | ||
because of the way we're using the `...` spread operator to collect the options | ||
into a single (brand new) object. This means that the dependency array will | ||
always be different and the effect will always run! | ||
|
||
`useEffect` iterates through each of our dependencies and checks whether they | ||
have changed and it uses `Object.is` to do so (this is effectively the same | ||
as `===`). This means that even if two objects have the same properties, they | ||
will not be considered equal if they are different objects. | ||
|
||
```tsx | ||
const options1 = { glare: true, max: 25, 'max-glare': 0.5, speed: 400 } | ||
const options2 = { glare: true, max: 25, 'max-glare': 0.5, speed: 400 } | ||
Object.is(options1, options2) // false!! | ||
``` | ||
|
||
So the easiest way to fix this is by switching from using an object to using the | ||
primitive values directly. This way, the dependency array will only change when | ||
the actual values change. | ||
|
||
So please update the `useEffect` to use the primitive values directly. Thanks! |
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,39 @@ | ||
/* | ||
Taken from the vanilla-tilt.js demo site: | ||
https://micku7zu.github.io/vanilla-tilt.js/index.html | ||
*/ | ||
.tilt-root { | ||
height: 150px; | ||
background-color: red; | ||
width: 200px; | ||
background-image: -webkit-linear-gradient(315deg, #ff00ba 0%, #fae713 100%); | ||
background-image: linear-gradient(135deg, #ff00ba 0%, #fae713 100%); | ||
transform-style: preserve-3d; | ||
will-change: transform; | ||
transform: perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1); | ||
} | ||
.tilt-child { | ||
position: absolute; | ||
width: 50%; | ||
height: 50%; | ||
top: 50%; | ||
left: 50%; | ||
transform: translateZ(30px) translateX(-50%) translateY(-50%); | ||
box-shadow: 0 0 50px 0 rgba(51, 51, 51, 0.3); | ||
background-color: white; | ||
} | ||
.totally-centered { | ||
width: 100%; | ||
height: 100%; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
} | ||
|
||
.count-button { | ||
width: 100%; | ||
height: 100%; | ||
background: transparent; | ||
border: none; | ||
font-size: 3em; | ||
} |
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,114 @@ | ||
import { useEffect, useRef, useState } from 'react' | ||
import * as ReactDOM from 'react-dom/client' | ||
import VanillaTilt from 'vanilla-tilt' | ||
|
||
interface HTMLVanillaTiltElement extends HTMLDivElement { | ||
vanillaTilt: VanillaTilt | ||
} | ||
|
||
function Tilt({ | ||
children, | ||
max = 25, | ||
speed = 400, | ||
glare = true, | ||
maxGlare = 0.5, | ||
}: { | ||
children: React.ReactNode | ||
max?: number | ||
speed?: number | ||
glare?: boolean | ||
maxGlare?: number | ||
}) { | ||
const tiltRef = useRef<HTMLVanillaTiltElement>(null) | ||
|
||
// 🐨 move this into the useEffect directly | ||
const vanillaTiltOptions = { | ||
max, | ||
speed, | ||
glare, | ||
'max-glare': maxGlare, | ||
} | ||
|
||
useEffect(() => { | ||
const { current: tiltNode } = tiltRef | ||
if (tiltNode === null) return | ||
VanillaTilt.init(tiltNode, vanillaTiltOptions) | ||
return () => tiltNode.vanillaTilt.destroy() | ||
// 🐨 instead of passing the options object here, pass each primitive option | ||
}, [vanillaTiltOptions]) | ||
|
||
return ( | ||
<div ref={tiltRef} className="tilt-root"> | ||
<div className="tilt-child">{children}</div> | ||
</div> | ||
) | ||
} | ||
|
||
function App() { | ||
const [count, setCount] = useState(0) | ||
const [options, setOptions] = useState({ | ||
max: 25, | ||
speed: 400, | ||
glare: true, | ||
maxGlare: 0.5, | ||
}) | ||
return ( | ||
<div className="app"> | ||
<form | ||
onSubmit={e => e.preventDefault()} | ||
onChange={event => { | ||
const formData = new FormData(event.currentTarget) | ||
setOptions({ | ||
max: formData.get('max') as any, | ||
speed: formData.get('speed') as any, | ||
glare: formData.get('glare') === 'on', | ||
maxGlare: formData.get('maxGlare') as any, | ||
}) | ||
}} | ||
> | ||
<div> | ||
<label htmlFor="max">Max:</label> | ||
<input id="max" name="max" type="number" defaultValue={25} /> | ||
</div> | ||
<div> | ||
<label htmlFor="speed">Speed:</label> | ||
<input id="speed" name="speed" type="number" defaultValue={400} /> | ||
</div> | ||
<div> | ||
<label> | ||
<input id="glare" name="glare" type="checkbox" defaultChecked /> | ||
Glare | ||
</label> | ||
</div> | ||
<div> | ||
<label htmlFor="maxGlare">Max Glare:</label> | ||
<input | ||
id="maxGlare" | ||
name="maxGlare" | ||
type="number" | ||
defaultValue={0.5} | ||
/> | ||
</div> | ||
</form> | ||
<br /> | ||
<Tilt {...options}> | ||
<div className="totally-centered"> | ||
<button className="count-button" onClick={() => setCount(c => c + 1)}> | ||
{count} | ||
</button> | ||
</div> | ||
</Tilt> | ||
</div> | ||
) | ||
} | ||
|
||
const rootEl = document.createElement('div') | ||
document.body.append(rootEl) | ||
ReactDOM.createRoot(rootEl).render(<App />) | ||
|
||
// 🤫 we'll fix this in the next step! | ||
// (ALMOST) NEVER DISABLE THIS LINT RULE IN REAL LIFE! | ||
/* | ||
eslint | ||
react-hooks/exhaustive-deps: "off", | ||
*/ |
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,6 @@ | ||
# Primitive Dependencies | ||
|
||
👨💼 This is probably one of the more annoying parts about dependency arrays. | ||
Luckily modern React applications don't need to reach for `useEffect` for many | ||
use cases (thanks to frameworks like Remix), but it's important for you to | ||
understand. |
Oops, something went wrong.