Appearance
Refs and the DOM (two different "ref" words)
In Vue,
ref()is reactive state and sometimes a template ref. In React,useRefis a mutable box that survives renders - and also how you grab DOM nodes.
useRef as a mutable box
tsx
import { useRef } from 'react'
function Timer() {
const ticks = useRef(0)
function onClick() {
ticks.current += 1
console.log(ticks.current)
}
return <button type="button" onClick={onClick}>Tick</button>
}Comments for the code above:
- Changing
.currentdoes not re-render (unlike Vueref) - Useful for interval IDs, previous values, anything imperative that isn't UI state
- If the UI must update, use
useState
DOM refs
tsx
function FocusInput() {
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
inputRef.current?.focus()
}, [])
return <input ref={inputRef} />
}As you can see - ref={inputRef} is the template ref equivalent of ref="inputRef" / const el = ref(null) in Vue.
forwardRef (when a parent needs a child's DOM node)
Custom components don't forward refs by default. If you wrap an <input> and want the parent to focus it:
tsx
import { forwardRef } from 'react'
const FancyInput = forwardRef<HTMLInputElement, { label: string }>(
function FancyInput({ label }, ref) {
return (
<label>
{label}
<input ref={ref} />
</label>
)
},
)Comments for the code above:
- Parent does
<FancyInput ref={inputRef} label="Email" /> - React 19 makes ref-as-prop easier in some cases - check your version;
forwardRefstill works everywhere
What I'd avoid
- Storing props/state copies in refs "to avoid re-renders" as a default strategy - usually wrong
- Touching the DOM for things React already does (toggling classes that could be state)
Refs are escape hatches. Use them when you must talk to the browser imperatively - then get back to declarative land.
Next: Context - provide/inject with more ceremony.
Enjoy!