How to use refs in React with Typescript
Asked Answered
H

16

297

I'm using Typescript with React. I'm having trouble understanding how to use refs so as to get static typing and intellisense with respect to the react nodes referenced by the refs. My code is as follows.

import * as React from 'react';

interface AppState {
    count: number;
}

interface AppProps {
    steps: number;
}

interface AppRefs {
    stepInput: HTMLInputElement;
}

export default class TestApp extends React.Component<AppProps, AppState> {

constructor(props: AppProps) {
    super(props);
    this.state = {
        count: 0
    };
}

incrementCounter() {
    this.setState({count: this.state.count + 1});
}

render() {
    return (
        <div>
            <h1>Hello World</h1>
            <input type="text" ref="stepInput" />
            <button onClick={() => this.incrementCounter()}>Increment</button>
            Count : {this.state.count}
        </div>
    );
}}
Hornbill answered 19/11, 2015 at 5:54 Comment(0)
M
316

If you’re using React 16.3+, the suggested way to create refs is using React.createRef().

class TestApp extends React.Component<AppProps, AppState> {
    private stepInput: React.RefObject<HTMLInputElement>;
    constructor(props) {
        super(props);
        this.stepInput = React.createRef();
    }
    render() {
        return <input type="text" ref={this.stepInput} />;
    }
}

When the component mounts, the ref attribute’s current property will be assigned to the referenced component/DOM element and assigned back to null when it unmounts. So, for example, you can access it using this.stepInput.current.

For more on RefObject, see @apieceofbart's answer or the PR createRef() was added in.


If you’re using an earlier version of React (<16.3) or need more fine-grained control over when refs are set and unset, you can use “callback refs”.

class TestApp extends React.Component<AppProps, AppState> {
    private stepInput: HTMLInputElement;
    constructor(props) {
        super(props);
        this.stepInput = null;
        this.setStepInputRef = element => {
            this.stepInput = element;
        };
    }
    render() {
        return <input type="text" ref={this.setStepInputRef} />
    }
}

When the component mounts, React will call the ref callback with the DOM element, and will call it with null when it unmounts. So, for example, you can access it simply using this.stepInput.

By defining the ref callback as a bound method on the class as opposed to an inline function (as in a previous version of this answer), you can avoid the callback getting called twice during updates.


There used to be an API where the ref attribute was a string (see Akshar Patel's answer), but due to some issues, string refs are strongly discouraged and will eventually be removed.


Edited May 22, 2018 to add the new way of doing refs in React 16.3. Thanks @apieceofbart for pointing out that there was a new way.

Mcclure answered 1/5, 2017 at 21:20 Comment(8)
Notice this is preferred way. Examples below with refs class attribute will be deprecated in upcoming React versions.Intemperate
Please note that this is already an old way :) current is to use React.createRef()Kingbolt
@Kingbolt Thanks for the heads up. Updated the answer to include the new way.Mcclure
I just don't see anything about typescript in your answer, I will add another answerKingbolt
Whoops. Had Typescript in my original answer but forgot to include it in the new one. Added it back and linked to your answer as well. Thanks.Mcclure
wow. i fell in love with tihs line: "private stepInput: React.RefObject<HTMLInputElement>;"Malawi
You can also use React.RefObject<HTMLElement>Male
When I do that I get the following flow error Cannot get 'React.RefObject' because property 'RefObject' is missing in object type [1].Flow(prop-missing). Any idea why?Quechua
V
129

React.createRef (class comp.)

class ClassApp extends React.Component {
  inputRef = React.createRef<HTMLInputElement>();
  
  render() {
    return <input type="text" ref={this.inputRef} />
  }
}

React.useRef (Hooks / function comp.)

a) Use readonly refs for React-managed DOM nodes:
const FunctionApp = () => {
  // note the passed-in `null` arg ----------------v
  const inputRef = React.useRef<HTMLInputElement>(null)
  return <input type="text" ref={inputRef} />
}

inputRef.current becomes a readonly property by initializing its value with null.

b) Use mutable refs for arbitrary stored values akin to instance variables:
const FunctionApp = () => {
  const renderCountRef = useRef(0)
  useEffect(() => {
    renderCountRef.current += 1
  })
  // ... other render code
}

Note: Don't initialize useRef with null in this case - it would make the renderCountRef type readonly (see example). If you need to provide null as initial value, do this:

const renderCountRef = useRef<number | null>(null)

Callback refs (both)

// Function component example, class analogue 
const FunctionApp = () => {
  const handleDomNodeChange = (domNode: HTMLInputElement | null) => {
    // ... do something with changed dom node.
  }
  return <input type="text" ref={handleDomNodeChange} />
}

Note: String Refs are considered legacy and omitted for the scope of this answer.

Playground sample

Vorticella answered 8/5, 2020 at 13:45 Comment(5)
What's the difference between useRef() as MutableRefObject<HTMLInputElement> and useRef<HTMLInputElement>(null) ?Lys
Good question - the current property of MutableRefObject<HTMLInputElement> can be modified, whereas useRef<HTMLInputElement>(null) creates a RefObject type with current marked as readonly. The former can be used, if you need to change current DOM nodes in refs yourself, e.g. in combination with an external library. It can also be written without as: useRef<HTMLInputElement | null>(null). The latter is a better choice for React managed DOM nodes, as used in most cases. React stores the nodes in the refs itself and you don't want to fumble around changing these values.Vorticella
This should be the top answer since the use of hooks was addedChappy
It was passing the initial value of null that I was missing, thanks!Lardy
One may also do it this way: Declare an interface interface RefTypes { show: Function; hide: Function; } and then wherever the ref is used ==> const customRef = useRef<RefTypes >();Puck
C
40

If you're using React.FC, add the HTMLDivElement interface:

const myRef = React.useRef<HTMLDivElement>(null);

And use it like the following:

return <div ref={myRef} />;
Creditor answered 30/3, 2020 at 19:33 Comment(2)
Thanks. Another tip for anyone who comes across this is to check the Element. This example refers to the usage of a DIV element. A form for example would use - const formRef = React.useRef<HTMLFormElement>(null);Diffusive
Thank you thank you thank you thank you thank you thank you thank you thank you. Thank you.Principe
K
35

Since React 16.3 the way to add refs is to use React.createRef as Jeff Bowen pointed in his answer. However you can take advantage of Typescript to better type your ref.

In your example you're using ref on input element. So they way I would do it is:

class SomeComponent extends React.Component<IProps, IState> {
    private inputRef: React.RefObject<HTMLInputElement>;
    constructor() {
        ...
        this.inputRef = React.createRef();
    }

    ...

    render() {
        <input type="text" ref={this.inputRef} />;
    }
}

By doing this when you want to make use of that ref you have access to all input methods:

someMethod() {
    this.inputRef.current.focus(); // 'current' is input node, autocompletion, yay!
}

You can use it on custom components as well:

private componentRef: React.RefObject<React.Component<IProps>>;

and then have, for example, access to props :

this.componentRef.current.props; // 'props' satisfy IProps interface
Kingbolt answered 24/5, 2018 at 9:37 Comment(0)
N
33

One way (which I've been doing) is to setup manually :

refs: {
    [string: string]: any;
    stepInput:any;
}

then you can even wrap this up in a nicer getter function (e.g. here):

stepInput = (): HTMLInputElement => ReactDOM.findDOMNode(this.refs.stepInput);
Nevermore answered 19/11, 2015 at 6:0 Comment(3)
Thanks @basarat. I tried your solution but I'm getting this error 'Type Element is not assignable to type 'HTMLInputElement. Property accept is missing in type Element''Hornbill
Might be an issue with the newer version of the react-dom definitions. Use as assertion in the meantimeNevermore
Obviously any isn't mandatory here. Most examples I see use HTMLInputElement. Just stating the obvious, but if your ref is on a React component (i.e. PeoplePicker), you can use that component as the type to get typings.Arsenic
H
17

EDIT: This is no longer the right way to use refs with Typescript. Look at Jeff Bowen's answer and upvote it to increase its visibility.

Found the answer to the problem. Use refs as below inside the class.

refs: {
    [key: string]: (Element);
    stepInput: (HTMLInputElement);
}

Thanks @basarat for pointing in the right direction.

Hornbill answered 20/11, 2015 at 12:9 Comment(2)
I am still getting Property 'stepInput' does not exist on type '{ [key: string]: Component<any, any> | Element; }', when trying to access this.refs.stepInput.Tympanites
@NikSumeiko, you were getting that error because your refs object only had the [key: string] entry.Arsenic
T
7

FIRST ADD AN IMPORT

import React, { useRef } from "react";

THEN THIS

const studentCapacityRef = useRef<HTMLInputElement>(null);

OR THIS

const studentCapacityRef = useRef<HTMLAreaElement>(null);

OR THIS

const studentCapacityRef = useRef<HTMLDivElement>(null);

etc...

Tymothy answered 20/9, 2022 at 11:27 Comment(0)
H
4

For those looking on how to do it when you have an array of elements:

const textInputRefs = useRef<(HTMLDivElement | null)[]>([])

...

const onClickFocus = (event: React.BaseSyntheticEvent, index: number) => {
    textInputRefs.current[index]?.focus()
};

...

{items.map((item, index) => (
    <textInput
        inputRef={(ref) => textInputs.current[index] = ref}
    />
    <Button
        onClick={event => onClickFocus(event, index)}
    />
}
Hannah answered 3/5, 2020 at 20:6 Comment(0)
L
2

To use the callback style (https://facebook.github.io/react/docs/refs-and-the-dom.html) as recommended on React's documentation you can add a definition for a property on the class:

export class Foo extends React.Component<{}, {}> {
// You don't need to use 'references' as the name
references: {
    // If you are using other components be more specific than HTMLInputElement
    myRef: HTMLInputElement;
} = {
    myRef: null
}
...
 myFunction() {
    // Use like this
    this.references.myRef.focus();
}
...
render() {
    return(<input ref={(i: any) => { this.references.myRef = i; }}/>)
}
Laliberte answered 22/1, 2017 at 3:26 Comment(0)
C
2

For typescript user no constructor required.

...

private divRef: HTMLDivElement | null = null

getDivRef = (ref: HTMLDivElement | null): void => {
    this.divRef = ref
}

render() {
    return <div ref={this.getDivRef} />
}

...

Carman answered 7/6, 2019 at 5:26 Comment(0)
W
2

If you wont to forward your ref, in Props interface you need to use RefObject<CmpType> type from import React, { RefObject } from 'react';

Whyte answered 10/7, 2019 at 15:6 Comment(0)
D
0

Lacking a complete example, here is my little test script for getting user input when working with React and TypeScript. Based partially on the other comments and this link https://medium.com/@basarat/strongly-typed-refs-for-react-typescript-9a07419f807#.cdrghertm

/// <reference path="typings/react/react-global.d.ts" />

// Init our code using jquery on document ready
$(function () {
    ReactDOM.render(<ServerTime />, document.getElementById("reactTest"));
});

interface IServerTimeProps {
}

interface IServerTimeState {
    time: string;
}

interface IServerTimeInputs {
    userFormat?: HTMLInputElement;
}

class ServerTime extends React.Component<IServerTimeProps, IServerTimeState> {
    inputs: IServerTimeInputs = {};

    constructor() {
        super();
        this.state = { time: "unknown" }
    }

    render() {
        return (
            <div>
                <div>Server time: { this.state.time }</div>
                <input type="text" ref={ a => this.inputs.userFormat = a } defaultValue="s" ></input>
                <button onClick={ this._buttonClick.bind(this) }>GetTime</button>
            </div>
        );
    }

    // Update state with value from server
    _buttonClick(): void {
    alert(`Format:${this.inputs.userFormat.value}`);

        // This part requires a listening web server to work, but alert shows the user input
    jQuery.ajax({
        method: "POST",
        data: { format: this.inputs.userFormat.value },
        url: "/Home/ServerTime",
        success: (result) => {
            this.setState({ time : result });
        }
    });
}

}

Dirndl answered 12/5, 2016 at 8:50 Comment(0)
S
0

From React type definition

    type ReactInstance = Component<any, any> | Element;
....
    refs: {
            [key: string]: ReactInstance
    };

So you can access you refs element as follow

stepInput = () => ReactDOM.findDOMNode(this.refs['stepInput']);

without redefinition of refs index.

As @manakor mentioned you can get error like

Property 'stepInput' does not exist on type '{ [key: string]: Component | Element; }

if you redefine refs(depends on IDE and ts version you use)

Strontianite answered 26/12, 2016 at 14:4 Comment(0)
S
0

Just to add a different approach - you can simply cast your ref, something like:

let myInputElement: Element = this.refs["myInput"] as Element
Sewel answered 12/2, 2017 at 14:29 Comment(0)
S
0

I always do this, in that case to grab a ref

let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs.input);

Sneak answered 4/4, 2017 at 20:26 Comment(1)
let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs['input']);Sneak
J
-1
class SelfFocusingInput extends React.Component<{ value: string, onChange: (value: string) => any }, {}>{
    ctrls: {
        input?: HTMLInputElement;
    } = {};
    render() {
        return (
            <input
                ref={(input) => this.ctrls.input = input}
                value={this.props.value}
                onChange={(e) => { this.props.onChange(this.ctrls.input.value) } }
                />
        );
    }
    componentDidMount() {
        this.ctrls.input.focus();
    }
}

put them in an object

Jessen answered 1/8, 2016 at 10:35 Comment(2)
Please explain your answerAtropine
This answer is setting ctrls.input to a strongly typed element, which is the strongly-typed way to go. This is a better "Typescript" choice.Maddux

© 2022 - 2024 — McMap. All rights reserved.