What's the difference between `useRef` and `createRef`?
Asked Answered
I

6

299

I was going through the hooks documentation when I stumbled upon useRef.

Looking at their example…

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

…it seems like useRef can be replaced with createRef.

function TextInputWithFocusButton() {
  const inputRef = createRef(); // what's the diff?
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputRef.current.focus();
  };
  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

Why do I need a hook for refs? Why does useRef exist?

Illyria answered 10/2, 2019 at 20:27 Comment(0)
O
306

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);

Edit 1rvwnj71x3

Opt answered 10/2, 2019 at 20:39 Comment(1)
d= (^-^ ) good point that ref is not limited, and can even hold a simple number; But why is .current required (unlike useState hook)? Found the reason: just to make .current passable by reference, like a real class's field, without strange setter. (LOL, I wonder how far slower functional vs real class is nowadays.)Jeremiahjeremias
F
114

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.

It is sufficient to use 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 basically useState({current: initialValue })[0].

With insights from the tldr section, we now can further conclude:

useRef(null) is basically useState(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 mutating 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.

Fino answered 6/5, 2020 at 15:2 Comment(2)
"useRef(null) is basically useState(React.createRef())[0]" ... More like useRef(null) is basically useState(() => React.createRef())[0], no? Otherwise React.createRef() would run every render.Disappearance
The lazy initial state is mainly for performance reasons (argument in non-function form would be disregarded in subsequent re-renders). As the example is used for illustration purposes and hypothetical, I guess it's easier to understand without function form. Good hint though.Fino
C
71

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.

Crockery answered 10/2, 2019 at 20:36 Comment(4)
There's a comment here by one of the React devs explaining that this is how it works: reddit.com/r/reactjs/comments/a2pt15/… I'd be interested to know what you think is incorrect about this answer.Crockery
I saw that link before I tried answering this question, where does it state this fact in the link you have shared? I couldn't find it? :)Neoteric
The link I shared shows a simplified implementation of useRef, posted by one of the React developers. It is not the same as simply calling createRef, as createRef isn't a hook and doesn't persist any state between calls. Ryan Cogswell's answer also has a good example of the differences.Crockery
My understanding from that context only inferred that useRef is a custom hook that uses createRef inside. Thank you for sharing the knowledge.Neoteric
Z
6

Just to highlight a purpose:

createRef is as simple as return {current: null}. It's a way to handle ref= prop in most modern way and that's it(while string-based is toooo way magic and callback-based looks too verboose).

useRef keeps some data before renders and changing it does not cause re-render(as useState does). They are rarely related. Everything you expect for class-based component go to instance fields(this.* =) looks like candidate to be implemented with useRef in functional components.

Say useCallback works as bounded class methods(this.handleClick = .....bind(this)) and may be re-implemented(but we should not re-invent the wheel for sure) with useRef.

Another examples are DOM refs, timeout/interval IDs, any 3rd party libraries' identifiers or references.

PS I believe React team better chose different naming for useRef to avoid confusion with createRef. Maybe useAndKeep or even usePermanent.

Zucker answered 16/9, 2019 at 6:53 Comment(0)
C
1

A ref is a plain JS object { current: }.

React.useRef(initValue) return a ref { current: initValue }
    it is remember ref value across multiple render of function component.
It is advise to use in Function component
    
React.createRef(initValue) also return a ref  { current: initValue }
    it is not remember ref value across multiple render of function components. It is advise to use in class based component
Cioffred answered 19/4, 2022 at 11:58 Comment(0)
G
0

useRef and forwardRef are two different things in React with different use cases.

import React, { useRef } from 'react';

const App = () => {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Focus input</button>
    </div>
  );
};

export default App;

And on the other hand:

import React, { forwardRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
  return (
    <input type="text" ref={ref} />
  );
});

const App = () => {
  const inputRef = React.createRef();

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={handleClick}>Focus
   </div>
)
Gujarat answered 3/4, 2023 at 15:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.