useRef
The useRef
hook creates a mutable container that persists for the lifetime of the component. It’s primarily used for:
- Accessing DOM elements directly
- Storing mutable values that don’t trigger re-renders when changed
- Keeping track of previous values or state across renders
Basic Usage
val ref = useRef[T](initialValue)
Where: - T
is the type of value the ref will hold - initialValue
is the initial value for ref.current
(defaults to null
) - The returned object has a single mutable property, current
, which can be read and written to
Accessing DOM Elements
The most common use case for useRef
is to access DOM elements:
val InputWithFocus = () => {
val inputRef = useRef[dom.html.Input]()
def handleButtonClick(): Unit = {
// Access the DOM element directly
inputRef.current.focus()
}
div(
input(
ref := inputRef,
typ := "text"
),
button(
onClick := (() => handleButtonClick()),
"Focus the input"
)
)
}
Persisting Values Between Renders
Unlike state, changing a ref’s value doesn’t cause a re-render:
val RenderCounter = () => {
// This counts renders but doesn't appear in the output
val renderCount = useRef(0)
renderCount.current += 1
val (count, setCount, _) = useState(0)
div(
p(s"Count: $count"),
p(s"Render count: ${renderCount.current}"),
button(
onClick := (() => setCount(count + 1)),
"Increment"
)
)
}
Keeping Track of Previous Values
Refs can be used to remember the previous value of a prop or state:
def usePrevious[T](value: T): T = {
val ref = useRef[T]()
useEffect(() => {
ref.current = value
}, Seq(value))
ref.current
}
val CounterWithPrevious = () => {
val (count, setCount, _) = useState(0)
val previousCount = usePrevious(count)
div(
p(s"Count: $count"),
p(s"Previous count: ${Option(previousCount).getOrElse("none")}"),
button(
onClick := (() => setCount(count + 1)),
"Increment"
)
)
}
Avoiding Re-Renders with Mutable State
When you need to update state frequently but don’t want to trigger re-renders, you can use a ref to track the current value and update the state less frequently:
val MouseTracker = () => {
val [position, setPosition, _] = useState((0, 0))
val positionRef = useRef((0, 0))
useEffect(() => {
const handleMouseMove = (e: dom.MouseEvent) => {
// Update ref without causing re-render
positionRef.current = (e.clientX, e.clientY)
}
// Update state (and re-render) every 100ms
val interval = dom.window.setInterval(() => {
setPosition(positionRef.current)
}, 100)
document.addEventListener("mousemove", handleMouseMove)
() => {
document.removeEventListener("mousemove", handleMouseMove)
dom.window.clearInterval(interval)
}
}, Seq())
div(s"Mouse position: ${position._1}, ${position._2}")
}
Using with Complex DOM Elements
You can specify the exact type of DOM element you want to reference:
val VideoPlayer = () => {
val videoRef = useRef[dom.html.Video]()
def handlePlay(): Unit = {
videoRef.current.play()
}
def handlePause(): Unit = {
videoRef.current.pause()
}
div(
video(
ref := videoRef,
width := "640",
height := "360",
controls := true,
source(
src := "video.mp4",
typ := "video/mp4"
)
),
div(
button(onClick := (() => handlePlay()), "Play"),
button(onClick := (() => handlePause()), "Pause")
)
)
}
Forwarding Refs to Custom Components
You can use forwardRef
to pass a ref through a component to one of its children:
case class CustomInputProps(
label: String,
placeholder: String = ""
)
val CustomInput = forwardRef[CustomInputProps] { (props, ref) =>
div(
cls := "form-control",
label(props.label),
input(
ref := ref.asInstanceOf[RefHook & { type RefType <: dom.html.Input }],
typ := "text",
placeholder := props.placeholder
)
)
}
// Usage in a parent component
val FormWithRef = () => {
val inputRef = useRef[dom.html.Input]()
div(
CustomInput <> (CustomInputProps("Username", "Enter username"), inputRef),
button(
onClick := (() => inputRef.current.focus()),
"Focus input"
)
)
}
Measuring DOM Elements
Refs can be used to measure DOM elements after they render:
val MeasureExample = () => {
val divRef = useRef[dom.html.Div]()
val (dimensions, setDimensions, _) = useState((0, 0))
useEffect(() => {
if (divRef.current != null) {
setDimensions((divRef.current.clientWidth, divRef.current.clientHeight))
}
}, Seq())
div(
ref := divRef,
cls := "measured-div",
"This div's dimensions will be measured"
),
p(s"Width: ${dimensions._1}px, Height: ${dimensions._2}px")
}
Using Refs with Callbacks
You can combine refs with callbacks for more complex interactions:
val ScrollToTopButton = () => {
val buttonRef = useRef[dom.html.Button]()
val (isVisible, setIsVisible, _) = useState(false)
useEffect(() => {
val handleScroll = () => {
setIsVisible(dom.window.scrollY > 100)
}
dom.window.addEventListener("scroll", handleScroll)
() => dom.window.removeEventListener("scroll", handleScroll)
}, Seq())
if (isVisible) {
button(
ref := buttonRef,
cls := "scroll-to-top",
onClick := (() => dom.window.scrollTo(0, 0)),
"↑"
)
} else {
null
}
}
Implementation Details
In Fluxus, useRef
is implemented as a hook that creates a RefHook
object with a mutable current
property. Unlike state hooks, updating a ref’s current value doesn’t trigger a re-render.
The type parameter T
ensures type safety when accessing the current
property, providing better development experience and preventing runtime errors.