Recently I’ve got this message on Twitter
Instead of replying, I’ve decided to write down this “short post” about how to handle React DOM refs and ref forwarding with TypeScript for anyone new to React and TypeScript as I didn’t found this resource anywhere online.
Disclaimer:
Don’t expect to learn all the why’s and how’s about React refs within this blogpost ( you can find all that info in excellent React docs).
This post will try to mirror those docs a bit for easy context switching.
What are Refs in React ?
Refs provide a way to access DOM nodes or React elements created in the render method.
Let’s create some React refs with TypeScript 👌👀
Creating Refsclass MyComponent extends Component {
// we create ref on component instance
private myRef = createRef()
render() {
return <div ref={this.myRef} />
}
}
But with that component definition, we get compile error:
[ts]
Type 'RefObject<{}>' is not assignable to type 'RefObject<HTMLDivElement>'.
Uh oh? What’s going on here ? TypeScript has very good type inference especially within JSX, and because we are using ref
on an <div>
element, it knows that the ref needs to be a type of HTMLDivElement
. So how to fix this error?
React.createRef()
is an generic function
// react.d.ts
function createRef<T>(): RefObject<T>
What about the return type, the RefObject<T>
? Well that's just a Maybe
type with following interface
// react.d.ts
interface RefObject<T> {
// immutable
readonly current: T | null
}
With that covered, you already know how to make our code valid right ? 👀 We need to explicitly set the generic value for createRef
:
When a ref is passed to an element in
render
, a reference to the node becomes accessible at thecurrent
attribute of the ref.
If we wanna access our previously defined ref value, all we need to do is to get the current
value from the ref object
const node = this.myRef.current
Although, our node
variable is gonna be a Maybe type 👉 HTMLDivElement
OR null
( remember? RefObject<T>
interface... ).
So if we would like to execute some imperative manipulation with that node
we just couldn't do:
class MyComponent extends Component {
private myRef = React.createRef<HTMLDivElement>() focus() {
const node = this.myRef.current
node.focus()
}
}
with that code, we would get an compile error
[ts] Object is possibly 'null'.
const node: HTMLDivElement | null
You may think right now: “this is annoying, TypeScript sucks…” Not so fast partner 😎! TypeScripts just prevents you to do a programmatic mistake here which would lead to runtime error, even without reading a line of the docs ❤️.
What React docs say about current
value ?
React will assign the current property with the DOM element when the component mounts, and assign it back to null when it unmounts. ref updates happen before componentDidMount or componentDidUpdate lifecycle hooks.
That’s exactly what TS told you by that compile error! So to fix this, you need to add some safety net, an if
statement is very appropriate here ( it will prevent runtime errors and also narrow type definition by removing null
):
Also we get autocomplete to whole HTMLDivElement
DOM api. Lovely!
Curious reader may ask:
What about accessing refs within
componentDidMount
if we don't wanna encapsulate our imperative logic within a method ( because we are messy/bad programmers 😇 ) ?
I hear you… Because we know that our refs current
value is definitely gonna be available within componentDidMount
, we can use TypeScript's Non-null assertion operator 👉 👉 👉 !
That’s it!
Adding a Ref to a Class ComponentBut hey, I really recommend encapsulating the logic to separate method with descriptive method name. Ya know readable code without comments and stuff 🖖 …
If we wanted to wrap our MyComponent above to simulate it being focused immediately after mounting, we could use a ref to get access to the MyComponent instance and call its focus
method manually:
import React, { createRef, Component } from 'react'class AutoFocusTextInput extends Component {
// create ref with explicit generic parameter
// this time instance of MyComponent
private myCmp = createRef<MyComponent>() componentDidMount() {
// @FIXME
// non null assertion used, extract this logic to method!
this.textInput.current!.focus()
} render() {
return <MyComponent ref={this.textInput} />
}
}
Note I: this only works if
MyComponent
is declared as a classNote II: we get access to all instance methods as well + top notch DX thanks to TypeScript
Beautiful isn’t it ? 🔥
Refs and Functional ComponentsYou may not use the ref attribute on functional components because they don’t have instances
You can, however, use the ref attribute inside a functional component as long as you refer to a DOM element or a class component:
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4