React: updating controlled child component - reactjs

I'm trying to create some React components using TypeScript, one of which handles user input (let's call it MyCtrl), and the other reacting to this user input and updating the backend (MyUpdatingCtrl), both being controlled components. My issue is that when updating the backend fails I need to revert the value in the user input component, but since it keeps the value in its state I can't update it from the outer component. So how do I handle this case correctly?
Note that I simplified my situation to explain my issue more easily. The following bit of code does not represent my actual project, but illustrates the issue in the simplest way I could think of.
MyCtrl:
export interface MyCtrlProps {
Value: string;
OnValueChanged: (newValue: string) => void;
}
interface MyCtrlState {
CurrentValue: string;
}
export class MyCtrl extends React.Component<MyCtrlProps, MyCtrlState>{
constructor(props: MyCtrlProps) {
super(props);
this.state = { CurrentValue: props.Value };
}
render() {
return <input onChange={this.onInputChanged} value={this.state.CurrentValue} />;
}
private onInputChanged = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ ...this.state, CurrentValue: e.target.value },
() => this.props.OnValueChanged(this.state.CurrentValue));
};
}
MyUpdatingCtrl:
export interface MyUpdatingCtrlProps {
Value: string;
}
interface MyUpdatingCtrlState {
CurrentValue: string;
PreviousValue: string;
}
export class MyUpdatingCtrl extends React.Component<MyUpdatingCtrlProps, MyUpdatingCtrlState>{
constructor(props: MyUpdatingCtrlProps) {
super(props);
this.state = {
CurrentValue: props.Value,
PreviousValue: props.Value
};
}
render() {
return <MyCtrl Value={this.state.CurrentValue} OnValueChanged={this.onValueChanged} />
}
private onValueChanged = (newValue: string): void => {
try {
// update backend
}
catch (error) {
// How do I reset the value of MyCtrl to the previous value here?
}
};
}
Setting the state.CurrentValue in the catch block inside MyUpdatingCtrl.onValueChanged of course won't update the value in MyCtrl, so what should I do?

first of all, you can't get the updated state like that, use
this.props.OnValueChanged(e.target.value);
because setState is an async function and can't get its updated value in the function you are calling it except by using the callback method

Related

react state not updated even inside callback

I'm having problem setting state. I've to pass extra classes to CardComponent on click, on click is working fine, setState is called, and the callback executes, but state is not mutated (as logs clearly deny that state is updated - see onSelect below). How could I fix that?
Flow is that if an item is selected, parent component resets the array with selected property set on each item (the selected result object gets selected set to true), that works fine and list is reset, selected is highlighted. Then if user clicks on another item (SearchResult component), it should update itself and apply extra classes. This time it's guarenteed that parent would not reset the list.
import { Component, ReactNode } from "react";
import { Subject, Subscription } from "rxjs";
import CardComponent from "../../utils/card.component";
export interface SearchResultComponentClickEvent {
(_id: string): void;
}
export interface SearchResultComponentProps {
result: any;
full: boolean;
onClick: SearchResultComponentClickEvent;
onSelect: Subject<string>;
}
interface SearchResultComponentState {
selected: string;
extraClasses: string;
}
export default class SearchResult extends Component<
SearchResultComponentProps,
SearchResultComponentState
> {
onSelectSubscription: Subscription = null;
constructor(props: SearchResultComponentProps) {
super(props);
let extraClasses = "mb-4";
if (this.props.result.selected) extraClasses += " border border-primary";
this.state = {
selected: this.props.result.selected,
extraClasses,
};
this.onSelect = this.onSelect.bind(this);
}
componentDidMount(): void {
this.onSelectSubscription = this.props.onSelect.subscribe((_id: string) => {
this.setState({
selected: '',
extraClasses: 'mb-4'
});
});
}
componentWillUnmount(): void {
this.onSelectSubscription.unsubscribe();
}
onSelect() {
this.setState({
selected: this.props.result._id,
extraClasses: "mb-4 border border-primary",
}, () => {
console.log(this.state);
// logs: { selected: '', extraClasses: "mb4 " }
});
this.props.onClick(this.props.result._id);
}
view(): ReactNode {
return <div>Simple view</div>
}
fullView(): ReactNode {
return <div>Extended view</div>;
}
render(): ReactNode {
return (
<CardComponent
padding={0}
onClick={this.onSelect}
extraClasses={this.state.extraClasses}
key={this.state.extraClasses}
>
{this.props.full ? this.fullView() : this.view()}
</CardComponent>
);
}
}
You have changed the state but setState is asynchronous in nature, changing the state will not immediately give its updated value https://facebook.github.io/react/docs/react-component.html#setstate, if you can show snippet of what exactly are you doing CardComponent, can give a more reliable solution.
If you still want to go with this approach, you can change your onSelect to something like this.
onSelect() {
this.setState({
selected: this.props.result._id,
extraClasses: "mb-4 border border-primary",
}, function() {
console.log(this.state);
this.props.onClick(this.props.result._id);
});
}
this will wrap or attached a callback to the state, which guarantees it, but it has its own side effects.
I would also recommend componentDidUpdate over this solution, more precise.
The problem is probably in the rxjs Subscription object.
Insert console.log and check...
componentDidMount(): void {
this.onSelectSubscription = this.props.onSelect.subscribe((_id: string) => {
// Log
console.log('componentDidMount-onSelectSubscription', this.state);
this.setState({
selected: '',
extraClasses: 'mb-4'
});
});
}

How to update state into Provider context in class in React

i have a problemn, i need to pass an array to class DragAndDropProvider but not working, i can see value in console log but not is possible update state with value, received error as:
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
moment in that i call the class
Index
const documents = UseGetDocuments();
const { state, setItemsArray } = useContext(DragAndDropContext);
setItemsArray(documents);
DragAndDropProvider
export class DragAndDropProvider extends Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
items: [],
moveItem: this.moveItem,
setItems: this.setItems
};
}
an_independent_function(documents: any) {
console.log(documents);
this.setState({ items: documents })
}
render() {
return (
<DragAndDropContext.Provider
value={
{
state: this.state,
setItemsArray: (documents: any) => {
this.an_independent_function(documents);
},
}
}>
{this.props.children}
</DragAndDropContext.Provider>
);
}
When using Class components you need to bind 'this' for it to work in your callback function.
constructor(props: any) {
...
this.an_independent_function = this.an_independent_function.bind(this);
}

React TypeScript: How to make a ref? [duplicate]

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>
);
}}
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.
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
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);
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
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} />;
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.
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)}
/>
}
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; }}/>)
}
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} />
}
...
If you wont to forward your ref, in Props interface you need to use RefObject<CmpType> type from import React, { RefObject } from 'react';
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 });
}
});
}
}
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)
Just to add a different approach - you can simply cast your ref, something like:
let myInputElement: Element = this.refs["myInput"] as Element
I always do this, in that case
to grab a ref
let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs.input);
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...
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

'ValueChanging' does not exist on type 'Readonly<{}>'

I'm trying to implement a handler, in React, for a survey implemented in SurveyJS. This is for multiple-choice questions that may have answers like "None of the Above" or "Prefer Not To Answer". If one of those answers is selected, all other answers should be blanked out, and if a different answer is selected, these checkboxes should be cleared out. I'm doing fine with either one of these individually, but having problems with a question where both options are present, specifically when switching back & forth between the two special options.
What I think is happening is that when one answer triggers the handler, and unchecks the other checkbox, it triggers the handler again. My solution is to set a state that indicates when the handler is in the middle of this process, and not do it again at that time.
I got a JS solution for this here: https://github.com/surveyjs/editor/issues/125 - and below is my attempt to convert it to React. (Just the relevant parts of the code included.)
However, on compile, it gives the following error:
ERROR in [at-loader] ./src/components/Survey/SurveyContainer.tsx:55:19
TS2339: Property 'ValueChanging' does not exist on type
'Readonly<{}>'.
I can't find anything about this specific error. Other references to the state (i.e. where I'm setting it) are working. Why can't I read it?
Thanks!
Component:
import * as React from 'react';
import { Component } from 'react';
import { Survey, surveyStrings } from 'survey-react';
import 'whatwg-fetch';
import Marked from '../Marked';
import * as style from './style';
interface Props {
surveyJson: object;
complete: boolean;
resultMarkdown: string;
surveyTitle: string;
sendSurveyAnswers: (answers: object[]) => void;
noneOfTheAboveHandler: (survey: object, options: object) => void;
}
Survey.cssType = 'standard';
surveyStrings.progressText = '{0}/{1}';
surveyStrings.emptySurvey = '';
export default class SurveyComponent extends Component<Props, {}> {
render() {
const { surveyJson, sendSurveyAnswers, complete, surveyTitle, resultMarkdown, noneOfTheAboveHandler } = this.props;
return (
<style.Wrapper>
{ surveyJson && (!complete) &&
<style.SurveyWrapper>
<style.SurveyTitle>{ surveyTitle }</style.SurveyTitle>
<style.Survey>
<Survey
onValueChanged={ noneOfTheAboveHandler }
css={ style.surveyStyles }
json={ surveyJson }
onComplete={ sendSurveyAnswers }
/>
</style.Survey>
</style.SurveyWrapper>
}
{ complete &&
<style.Results>
<Marked content={resultMarkdown} />
</style.Results>
}
</style.Wrapper>
);
}
}
Container:
import * as React from 'react';
import { Component } from 'react';
import { connect } from 'react-redux';
import SurveyComponent from './SurveyComponent';
interface Props {
id: string;
surveyJson: object;
complete: boolean;
resultMarkdown: string;
surveyTitle: string;
getSurveyQuestions: (id: string) => void;
sendSurveyAnswers: (answers: object[]) => void;
noneOfTheAboveHandler: (survey: object, options: object) => void;
clearSurvey: () => void;
}
class SurveyContainer extends Component<Props, {}> {
constructor(props) {
super(props);
this.state = { ValueChanging: false };
}
componentDidMount() {
this.noneOfTheAboveHandler = this.noneOfTheAboveHandler.bind(this);
this.props.getSurveyQuestions(this.props.id);
}
specialValueSelected(options, specialValue) {
const { question } = options;
const prevValue = question.prevValue;
const index = options.value.indexOf(specialValue);
this.setState({ ValueChanging: true });
//has special value selected
if(index > -1) {
//special value was selected before
if(prevValue.indexOf(specialValue) > -1) {
var value = question.value;
value.splice(index, 1);
question.value = value;
} else {
//special value select just now
question.value = [specialValue];
}
}
this.setState({ ValueChanging: false });
return index > -1;
}
noneOfTheAboveHandler(survey, options) {
const none = 'NA';
const preferNotToAnswer = 'PN';
const { question } = options;
if(this.state.ValueChanging) {
return;
}
if (!question || question.getType() !== 'checkbox') {
return;
}
if (!question.prevValue || !options.value) {
question.prevValue = options.value;
return;
}
if (!this.specialValueSelected(options,none)) {
this.specialValueSelected(options,preferNotToAnswer);
}
question.prevValue = options.value;
}
componentWillUnmount() {
this.props.clearSurvey();
}
render() {
return (
<SurveyComponent
noneOfTheAboveHandler={this.noneOfTheAboveHandler}
{...this.props}
/>
);
}
}
const mapStateToProps = (state, ownProps) => ({
surveyJson: state.survey.json,
answers: state.survey.answers,
resultMarkdown: state.survey.resultMarkdown,
complete: state.survey.complete,
surveyTitle: state.page && state.page.data ? state.page.data.title : ''
});
const mapDispatchToProps = dispatch => ({
getSurveyQuestions: id => dispatch({ type: 'GET_SURVEY_QUESTIONS', id }),
sendSurveyAnswers: answers => dispatch({ type: 'SEND_SURVEY_ANSWERS', answers: answers.data }),
clearSurvey: () => dispatch({ type: 'CLEAR_SURVEY' })
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(SurveyContainer);
The most likely cause for this is that you don't specify the type for your component's state in its class definition, so it defaults to {}. You can fix it by declaring interfaces for the types of props and state and providing these as type arguments to React.Component:
interface MyComponentProps { /* declare your component's props here */ }
interface MyComponentState { ValueChanging : boolean }
class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
constructor(props) {
...
You could provide the types directly between the < and >, but using interfaces usually leads to more readable code and promotes reuse. To prevent confusion with components it's also a good idea to use lower-case identifiers for the properties on props and state.
FYI: We have introduce this functionality into SurveyJS Library sometimes ago.
Here is the react example with "Select All" and "None of the Above" features, out of the box, without custom code.
Thank you!

How to use refs in React with Typescript

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>
);
}}
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.
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
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);
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
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} />;
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.
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)}
/>
}
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; }}/>)
}
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} />
}
...
If you wont to forward your ref, in Props interface you need to use RefObject<CmpType> type from import React, { RefObject } from 'react';
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 });
}
});
}
}
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)
Just to add a different approach - you can simply cast your ref, something like:
let myInputElement: Element = this.refs["myInput"] as Element
I always do this, in that case
to grab a ref
let input: HTMLInputElement = ReactDOM.findDOMNode<HTMLInputElement>(this.refs.input);
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...
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

Resources