Skip to main content

Ref

ref:

  • During reconciliation, ref changes/creation will be marked on fiber in flags.
  • During committing, react will detach/attach the ref by checking flags.
  • useRef() is a simple hook which just holds the ref object.

useRef Dispatcher

function mountRef<T>(initialValue: T) {
const hook = mountWorkInProgressHook()
const ref = {
current: initialValue,
}
Object.seal(ref)
hook.memoizedState = ref
return ref
}

function updateRef<T>(initialValue: T) {
const hook = updateWorkInProgressHook()
return hook.memoizedState
}

useRef Features

  • Mutable Value: useRef() is useful for for keeping any mutable value around. Updating reference values inside handlers/useEffect callbacks is good, updating reference values during rendering (outside callbacks) is bad.
  • Lifecycle Persisted Value: useRef() creates a plain JavaScript object, is persisted (stays the same) between component re-renderings.
  • Silent Value: update reference values don't trigger re-renderings.
  • Latest Value: useRef() read rendered props/state from the future. It's good to get latest value of a particular prop or state (the updated reference value is available right away).
export default function Example() {
const [count, setCount] = useState(0)
const latestCount = useRef(count)

useEffect(() => {
// Set the mutable latest value
latestCount.current = count
const timeout = setTimeout(() => {
// Read the mutable latest value
console.log(`You clicked ${latestCount.current} times`)
}, 3000)

return () => clearTimeout(timeout)
})

return <div>Example</div>
}

useRef Update Mechanism

  • Update a ref, no re-renderings happens.
  • Update a state, the deep rendering mechanism works to re-render components.
  • Store values in refs and have them updated, which is more efficient than useState (which can be expensive) when the values are to be updated multiple times within a second.
export default function UserAvatar({ src }: { src: string }) {
return <img src={src} alt="User Avatar" />
}
export default function Username({ name }: { name: string }) {
return <span>{name}</span>
}
export default function User() {
const user = useRef({
name: 'UserName',
avatarURL: 'https://avatar.com/avatar',
})

useEffect(() => {
const timeout = setTimeout(() => {
user.current = {
name: 'NewUserName',
avatarURL: 'https://avatar.com/newavatar',
}
}, 5000)

return () => clearTimeout(timeout)
})

// Only output once
console.log('Rendered.')

// Both children won't be re-rendered
// due to shallow rendering mechanism
return (
<div>
<Username name={user.name} />
<UserAvatar src={user.avatarURL} />
</div>
)
}

useRef Usage

If your component needs to store some value, but it doesn’t impact the rendering logic, choose refs:

  • Storing timeout IDs.
  • Storing and manipulating DOM elements (binding to HTMLElement).
  • Storing other objects that aren’t necessary to calculate the JSX: work with external systems or browser APIs.
  • ref can either be a state that does not need to change too often.
  • ref can either be a state that should change as frequently as possible but should not trigger full re-rendering of the component.
import { useRef, useState } from 'react'

export default function Stopwatch() {
const [startTime, setStartTime] = useState(null)
const [now, setNow] = useState(null)
const intervalRef = useRef(null)

function handleStart() {
setStartTime(Date.now())
setNow(Date.now())

clearInterval(intervalRef.current)
intervalRef.current = setInterval(() => {
setNow(Date.now())
}, 10)
}

function handleStop() {
clearInterval(intervalRef.current)
}

let secondsPassed = 0
if (startTime != null && now != null)
secondsPassed = (now - startTime) / 1000

return (
<>
<h1>
Time passed:
{secondsPassed.toFixed(3)}
</h1>
<button type="button" onClick={handleStart}>Start</button>
<button type="button" onClick={handleStop}>Stop</button>
</>
)
}
import { forwardRef, useImperativeHandle, useRef } from 'react'

interface Props {}

function MyInput({ ref, ...props }: Props) {
const realInputRef = useRef(null)
useImperativeHandle(ref, () => ({
// Only expose focus and nothing else
focus() {
realInputRef.current.focus()
},
}))
return <input {...props} ref={realInputRef} />
}

export default function Form() {
const inputRef = useRef(null)

function handleClick() {
inputRef.current.focus()
}

return (
<>
<MyInput ref={inputRef} />
<button type="button" onClick={handleClick}>Focus the input</button>
</>
)
}