NickyMeulemanNime
Metadata
  • Date

  • By

    • Nicky Meuleman
  • Tagged

  • Older post

    JSON-schemas are awesome

  • Newer post

    Skipping renders and memoization in React

Table of contents
  1. Basic usage
  2. Other usecases
    1. Referring to React Components
    2. Referring to a DOM Element inside a React Component
  3. createRef vs. useRef
    1. Instance variables
  4. Implementations
    1. createRef
    2. useRef

React refs

React refs

In React, a ref is an object with a mutable current property. That’s it. You can view the implementations below but they boil down to this: ref = { current: //something }
That current property is often set to hold a reference to a DOM Element, like an <audio>tag

Getting a reference to an element allows you to directly give it commands. This is useful for controlling media playback, managing focus and more.

It might be tempting to do this everywhere, but resist that temptation.

Basic usage

The goal is to get a reference to an <audio>tag. This will allow you to call .play() and .pause() on that element.

There are quite a few ways of achieving the same goal.
The steps are always the same however.

Several examples can be viewed in this codesandbox demo

  • Create the ref object
// inside the constructor
this.audioRef = React.createRef();
// this results in: this.audioRef = { current: null }
  • Attach a reference to a DOM Element to that ref object
// inside the render() method
<audio ref={this.audioRef} />
// this results in: this.audioRef = { current: <audio /> }
  • Use it
handlePlay = () => {
this.audioRef.play();
};

Other usecases

Referring to React Components

The examples above showed how to get a reference to a DOM Element. That’s not the only thing refs are good for. They can also point to a React Component object.

Codesandbox demo

Important note: you can only reference class based components this way

Referring to a DOM Element inside a React Component

What about when you made yourself a fancy Input component and want a ref to point directly at the <input>tag so you can call inputRef.current.focus()?

<Input ref={inputRef} />

There is a problem! It either points to the React Component object if your Input is a class component, or it throws a big fat error if your Input is a function component.

That big fat error is really helpful:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

React.forwardRef receives a function that gets called with the props and the ref your component received. It returns what your component would normally return. There, you can attach that ref to the specific DOM Element you want.

// in the file you want to use the ref
import Input from "./Input";
function App() {
const inputRef = React.useRef();
// ...
return (
<div>
<Input />
{/* attach the ref to our Input component */}
<Input ref={inputRef} />
<Input />
</div>
);
}
// in your Input.js file
export default React.forwardRef((props, ref) => {
return (
// ...
// forward the ref our Input component received to the HTML <input/>tag
<input ref={ref} placeholder={props.placeholder} />
// ...
);
});

This Codesandbox demo shows three Input components. One <input/>tag is focussed through a ref a component received and forwarded using React.forwardRef.

Having to wrap your function component in React.forwardRef can become quite tedious. In the future, this usage of forwardRef might become default behaviour

createRef vs. useRef

While createRef and useRef are very similar. There are some subtle differences.

useRef is a so called hook and is only available in function based components.

// in a function component
const myRef = React.useRef("my initial value");

useRef creates an object { current : initialValue } and returns it the first time it gets called. If that object already exists, useRef will return that existing object instead.

createRef will create an object { current : null } each time it is called.

// in the constructor of a class based component
this.myRef = React.createRef();
// even in a function based component
const myRef = React.createRef();

Instance variables

In class based components, if you want a value that can change but doesn’t trigger a render cycle. An instance variable is what you want. eg: this.palindrome = 'racecar' and later this.palindrome='pullup'

In function components, useRef can fill that role!
Since useRef(initialValue) returns an object with a mutable current property. There is nothing stopping us from using it like an instance variable.

const palindrome = useRef("racecar");
// const palindrome = { current: 'racecar' }
// ...
palindrome.current = "pullup";
// const palindrome = { current: 'pullup' }

This codesandbox uses useRef to point to the <form> DOM element and to an internal count variable.

Implementations

The implementation of both createRef and useRef in the official React codebase is surprisingly simple.

createRef

export function createRef() {
const refObject = {
current: null,
};
if (__DEV__) {
Object.seal(refObject);
}
return refObject;
}

The createRef implementation in the React codebase

useRef

export function useRef(initialValue) {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
let ref;
if (workInProgressHook.memoizedState === null) {
ref = { current: initialValue };
if (__DEV__) {
Object.seal(ref);
}
workInProgressHook.memoizedState = ref;
} else {
ref = workInProgressHook.memoizedState;
}
return ref;
}

The useRef implementation in the React codebase

Designed and developed by Nicky Meuleman

Built with Gatsby. Hosted on Netlify.