What's the difference between `useRef` and `createRef`?
The difference is that createRef
will always create a new ref. In a class-based component, you would typically put the ref in an instance property during construction (e.g. this.input = createRef()
). You don't have this option in a function component. useRef
takes care of returning the same ref each time as on the initial rendering.
Here's an example app demonstrating the difference in the behavior of these two functions:
import React, { useRef, createRef, useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [renderIndex, setRenderIndex] = useState(1);
const refFromUseRef = useRef();
const refFromCreateRef = createRef();
if (!refFromUseRef.current) {
refFromUseRef.current = renderIndex;
}
if (!refFromCreateRef.current) {
refFromCreateRef.current = renderIndex;
}
return (
<div className="App">
Current render index: {renderIndex}
<br />
First render index remembered within refFromUseRef.current:
{refFromUseRef.current}
<br />
First render index unsuccessfully remembered within
refFromCreateRef.current:
{refFromCreateRef.current}
<br />
<button onClick={() => setRenderIndex(prev => prev + 1)}>
Cause re-render
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
createRef
always returns a new ref, which you'd generally store as a field on a class component's instance. useRef
returns the same ref upon every render of a functional component's instance. This is what allows the state of the ref to persist between renders, despite you not explictly storing it anywhere.
In your second example, the ref would be re-created upon every render.
tldr
A ref
is a plain JS object { current: <some value> }
.
React.createRef()
is a factory returning a ref { current: null }
- no magic involved.
useRef(initValue)
also returns a ref { current: initValue }
akin to React.createRef()
. Besides, it memoizes this ref to be persistent across multiple renders in a function component.
React.createRef
in class components, as the ref object is assigned to an instance variable, hence accessible throughout the component and its lifecyle:
this.myRef = React.createRef(); // stores ref in "mutable" this context (class)
useRef(null)
basically is equivalent to useState(React.createRef())[0]
1.
1 Replace useRef
with useState
+ createRef
Following tweet has been enlightening for me:
useRef()
is basicallyuseState({current: initialValue })[0]
.
With insights from the tldr
section, we now can further conclude:
useRef(null)
is basicallyuseState(React.createRef())[0]
.
Above code "abuses" useState
to persist the returned ref from React.createRef()
. [0]
just selects the value part of useState
- [1]
would be the setter.
useState
causes a re-render in contrast to useRef
. More formally, React compares the old and new object reference for useState
, when a new value is set via its setter method. If we mutate the state of useState
directly (opposed to setter invocation), its behavior more or less becomes equivalent to useRef
, as no re-render is triggered anymore:
// Example of mutaing object contained in useState directly
const [ref] = useState({ current: null })
ref.current = 42; // doesn't cause re-render
Note: Don't do this! Use the optimized useRef
API instead of reinventing the wheel. Above is for illustration purposes.