React Hooks review #7: useRef
This is chapter seven of the React Hooks series. The goal was simple: summarize all the eleven official React hooks:
- The three basic Hooks:
useState
,useEffect
,useContext
. - The seven additional Hooks:
useReducer
,useCallback
,useMemo
,useRef
,useImperativeHandle
,useLayoutEffect
,useDebugValue
. - And understand the endless potential of custom hooks.
useRef
A mutable object whose
React.js.current
property is initialized to the passed argument. The returned object will persist for the full lifetime of the component.
Syntax:
const refContainer = useRef(initialValue);
- initialValue: The value stored between re-renderers.
- refContainer: Mutable ref object structured as
{current: initialValue}
.
DOM reminder and react.ref
On the prehistoric Vanilla days, we got and manipulated DOM elements with good old “document.getElementById
” or later on “document.querySelector
“. If we wanted to check or modify an element we used something like:
<script>
function changeMe(e,newText){
e.innerText = newText;
}
</script>
<div onclick="changeMe(this,'changed')">Loading...</div>
But once we got to the era of React, the simple getElementBy...
became an-anty-pattern, and for good reasons. This is why React created the ref: “a way to access DOM nodes or React elements created in the render method”.
<input type="text" ref={textInput} />
Note:
A guide to React refs: useRef and createRef
ref
‘s are escape hatches for React developers, and we should try to avoid using them if possible.
useRef for the DOM
useRef
is a hook created to “access a child imperatively”. That’s why it has the DOM structure of {current:someValue}
, which is suitable for DOM elements.
In the most common example, the useRef creates a mutable to reference the input field. This way we can easily do DOM manipulation on the element without re-render, like setting the focus.
const SetFocus = () => {
const textInput = React.useRef();
const focusTextInput = () => textInput.current.focus();
return (
<>
<input type="text" ref={textInput} />
<button onClick={focusTextInput}>Focus the text input</button>
</>
);
}
Another example, this time copying text from one input to the other:
const CopyInputValue = ({counter}) => {
const textInput = React.useRef();
const disabledText = React.useRef();
const focusTextInput = () => {
disabledText.current.value = textInput.current.value;
}
return (
<>
<input type="text" ref={textInput} />
<input type="text" ref={disabledText} disabled={true} />
<button onClick={focusTextInput}>Copy test</button>
</>
);
}
One more, that really shows the strength of useRef
. On this one, we give the parent control over its subcomponents.
const MyInput = (props)=>{
const inputRef = React.useRef();
const myOnChange = ()=>{
console.log('Sub change')
props.onBlur(inputRef.current);
}
return <input onBlur={myOnChange} ref={inputRef} />
}
const App = ()=>{
const myOnBlur = (inputRef)=> {
inputRef.value = "Hi";
}
return <MyInput onBlur={myOnBlur} />
}
This option becomes nicer once used with React.forwardRef
. This allows passing ref from one component to its sub-components directly, without callbacks.
const MyInput = React.forwardRef((props, ref)=>{
return <input {...props} ref={ref} />
})
const App = ()=>{
const myInputRef = React.useRef();
const myOnBlur = ()=> {
myInputRef.current.value = "Hi";
}
return <MyInput onBlur={myOnBlur} ref={myInputRef} />
}
Trying to pass the ref
without forwardRef
will block the ref, and throw a console error: “Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
”
useRef as state without re-rendering
Since useRef
returns a persistable object with the format of {current:someValue}
, It is similar to useState
with the same object type.
This example had two similar renrening counters. One with useState, and the other with useRef:
const Aka = () => {
const count = React.useRef(0);
count.current += 1;
const [myState,] = React.useState({current:0});
myState.current += 1;
return (
<div>
<p>I have rendered {count.current} times.</p>
<p>I have rendered {myState.current} times.</p>
</div>
);
}
As useState
does not re-render if an inner value had changed, if useRef.current
value had changed, the component will not re-render. This explains the second “official” usage for useRef
.
Is there something like instance variables?
Yes! The
Hooks FAQuseRef()
Hook isn’t just for DOM refs.
Or:
an object in which mutable information can be stored without triggering a re-render… particularly useful for working with DOM elements.
Understanding useRef
Usually, when you make something, you find out you can use it for different use cases. But this time, it seems as someone confused it as an official purpose of the element. But maybe I’m wrong. Anyway…
Since the current value does not change, it can be used to store values that don’t suppose to trigger a DOM change or re-render something
const StoringHiddenData = ({counter}) => {
const ref = React.useRef("Hi");
const flick = () => {
console.log(ref);
ref.current = "To Me";
}
return <p onClick={flick}>
Current value: {ref.current}
</p>
}
In this example, clicking the element will change the rev value. But until the parent component will re-render the StoringHiddenData (change the counter in the prop), the component will continue to show “Hi”. Once the counter changes, the inner text will show the updated “To Me” ref.current value.
If for some reason, you want to force a re-render, you can use the empty useState
trick. But if you find that you need to do it, you probably weren’t supposed to use useRef
in the first place.
const Aka = ({counter}) => {
const [, forceUpdate] = React.useState({});
const ref = React.useRef("Hi");
const flick = () => {
console.log(ref);
forceUpdate({});
ref.current = "To Me";
}
return <p onClick={flick}>
Current value: {ref.current}
</p>
}
Note, unlike useState
, you can’t add additional properties to the useRef
hook. So this example will throw an error:
const Aka = ({counter}) => {
const ref = React.useRef("Hi");
const flick = () => {
ref.someOtherProp = "BlaBla";
}
return <p onClick={flick}>
Current value: {ref.current}
</p>
}
Conclusion
useRef
looks like something meant only for ref usage. You can use it to store values when you don’t want to re-rendering the component. But useState
can be used in the same way, with less unexplainable limitations. But since React claims differently, I’m probably just missing something.