Understanding useRef by Building It
Thu Sep 19 2024
You must also be familiar with the useState
hook in order to understand the useRef
hook. States are held by the useState
and useRef
hooks. React provides two hooks that have the ability to hold state. The useState
exposes a tuple of value and dispatcher, as you may already be aware. Since the value is a copy of the external state that React maintains and we use the function to change the value, why does React keep an internal copy of the state? The React must take control of state variables because it is evident that when a state changes, it must re-render. Furthermore, we can refer to it as an immutable state in which updating the value requires calling a setter rather than changing the value directly.
The persistence of values between renders is a shared feature of both hooks. You may be wondering what is meant by "persist value between renders" at this point. Let's clarify with an example. The fact that the count remains unchanged when hide is enabled and then disabled again in the example above indicates that the value within the state is persistent, despite the fact that the component was re-rendered when the toggle state changed.
function Component() {
const [count, setCount] = useState(0);
const [hide, setHide] = useState(false);
return (
<div>
{hide && <p>{count}</p>}
<button onClick={() => setCount(count + 1)}>inc</button>
<button onClick={() => setHide(!hide)}>hide/unhide</button>
</div>
);
}
useState
and useRef
differ primarily in that useState
forces a re-render while "useRef" does not. Imagine, however, that useRef
is also an internally created state that uses only useState
! Yes, but why does the component that changes the ref value not re-render when it was just a wrapper, you ask? To grasp the concept, one must grasp how useState determines whether the value has changed. If the value is object
, its reference is verified using Object.is
, and for primitive values, the value is checked directly. If the same value is passed to state, however, React will not proceed with a re-render. Furthermore, useRef
always returns an object with the property current
, and we modify the value by modifying the current
property value. As a result, we never modified the object reference of the current state; rather, we only modified the property value. For a clearer understanding, let's create our own useRef
hook.
function useRef(initialValue) {
return useState({ current: initialValue })[0];
}
As you can see, the useRef
hook returns the state reference to you and internally stores a state with the value object
having a property current
that defaults to initialValue
passed. Therefore, the state never updates when you modify the ref value using the current
property; in fact, we never witness any re-renders when the value changes. This hook has very few use cases; the two that it does have are for referencing node elements and storing values that are retained between re-renders. I hope you now understand how hooks are made; we also do this with custom hooks.