Despite the short title line, I have a few interrelated questions about the sample below, borrowed from React official docs. I have trimmed down the original sample a bit.
On the very first App
render (when the page loads for the first time), the usePointerPosition
hook registers a pointermove
listener.
The useEffect
in the body of usePointerPosition
doesn’t run yet.
The <Dot>
JSX is returned from App
with a position of {0, 0}
.
Question: What happens when I move the mouse? Which code triggers the render: The usePointerPosition
hook, or the App
component?
My possibly incorrect and incomplete understanding is:
-
Mouse motion triggers the
handleMove
listener. -
This listener updates the state
position
in theusePointerPosition
hook. -
Question: Since
position
is a state private to theusePointerPosition
hook, why would it cause its parent,App
, to re-render, which is clearly re-rendering because I do see redDot
move on the screen? -
I can see that the private state
position
is being returned to theusePointerPosition
hook’s parent,App
. But becausepos1
inApp
, which stores the current mouse position, isn’t a state inApp
and merely a local variable, why shouldApp
re-render itself as I move the mouse? -
Question: In general, is some custom non-GUI hook,
useXyz
…a. that only holds private state by using
useState
in its body;b. that doesn’t interact with its parent via callbacks passed to it
via props; andc. that has no
useEffect
s in its body
… capable of triggering a render of its parent component?
import React, { useEffect, useState } from 'react'
export default function App() {
const pos1 = usePointerPosition()
const pos2 = useDelayedValue(pos1, 100)
return (
<>
<Dot position={pos1} opacity={1} />
</>
)
}
function usePointerPosition() {
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
function handleMove(e) {
setPosition({ x: e.clientX, y: e.clientY })
}
window.addEventListener('pointermove', handleMove)
return () => window.removeEventListener('pointermove', handleMove)
}, [])
return position
}
function useDelayedValue(value, delay) {
// TODO: Implement this Hook
return value
}
function Dot({ position, opacity }) {
return (
<div
style={{
position: 'absolute',
backgroundColor: 'pink',
borderRadius: '50%',
opacity,
transform: `translate(${position.x}px, ${position.y}px)`,
pointerEvents: 'none',
left: -20,
top: -20,
width: 40,
height: 40
}}
/>
)
}
2
Answers
The essence of hooks is side effect hanging on the fiber node.
So when you call
useState
, it create a side effect node and hang on the fiber node ofApp
, callsetPosition
will re-renderApp
Question: Since position is a state private to the usePointerPosition hook, why would it cause its parent, App, to re-render, which is clearly re-rendering because I do see red Dot move on the screen?
Answer: Position is a state inside the hook but you need to think it as way to connect hook state with the component that use the hook (in this case App) so all the life cycle is connected (if the hook state changes it’s going to trigger the update in the component that is using that hook)
if not it wouldn’t make sense having or using hooks
Question: I can see that the private state position is being returned to the usePointerPosition hook’s parent, App. But because pos1 in App, which stores the current mouse position, isn’t a state in App and merely a local variable, why should App re-render itself as I move the mouse?
Answer: It’s a variable that is holding the state provided by the hook and each time a setState is triggered it triggers the react life cycle in the component that is using the hook
Question: In general, is some custom non-GUI hook, useXyz…
a. that only holds private state by using useState in its body;
b. that doesn’t interact with its parent via callbacks passed to it via props; and
c. that has no useEffects in its body
Answer: Hooks can do whatever you want, think about it as a piece of code that you can reuse and will be part of the component that is calling it (triggering it’s life cycle) (even further you should be able to copy your hook code and paste it in the consumer component and it should still work)
A little of historical context of hooks
Prev to hooks the only way to add reusable logic was creating a wrapper component that wraps the component that will use that logic (in this example a creating a wrapper of
<App>
) these components had state and pass them as a props to<App>
, but that old way to do that was confusing and implies to create components only to add reusable logic (that commonly didn’t generate new UI-JSX-HTML)Custom Hooks are made to keep the components simple and still use reusable logic through the components that need that logic