TypeScript allows us to define types for our React components, props, state, and other objects, assisting us in catching type-related errors early in the development process. While we can solely use React, using TypeScript offers advantages in terms of type safety, code development, and better integration with the React framework, leading to more robust and maintainable code.
Manipulating or interacting with DOM elements rendered by a React component is a common task. For example, any web app involves an extensive use of forms, and working with forms requires us to read DOM (Document Object Model) values. These values can be from an input field and be stored in the state, validated on the frontend, or sent to the server. For such scenarios, the concept of React refs is used to access DOM elements associated with a React component.
Note: To learn more about how React refs work, visit this Answer.
Before diving into the main steps of this task, let’s see what the result will look like after the implementation is complete.
Using React refs in TypeScript requires a couple of additional changes to the standard React code we see when working with a basic React application. Le’s take a look at the steps to implement these changes successfully.
If we don’t have an existing React app with TypeScript on our systems, we can create one using the following commands in the terminal, one after the other. We can use any project name instead of the <project-name>
tag.
npx create-react-app <project-name> --template typescript
Note: The
--template typescript
flag is responsible for setting up a React app with TypeScript support including itsand configuration files Details of this are present in the tsconfig.json file when the app is created. build processes. Details of this are found in the package.json file when the app is created.
Form
componentNext, we will create a simple form component via a Form
class, defining interfaces that specify the Form
class extends the React.Component
, making it a component class. The code for this is shown below:
//The interfaces states and properties specify the additional properties that the form component accepts// Define an interface for the component's stateinterface States {name: string;}// Define an empty interface for the component's propertiesinterface Properties {}// Create a class component named "Form" that extends the "Component" classexport class Form extends Component<Properties, States> {// Declare a private field to hold a reference to the name input elementprivate nameInputRef: RefObject<HTMLInputElement>;// superclass constructor function that takes "props" as an argument which is the constructor of the parent classconstructor(props: Properties) {// Call the constructor of the parent class "Component" with the "props"super(props);// Initialize the component's state with an empty "name" propertythis.state = {name: '',};// Create a ref for the name input elementthis.nameInputRef = React.createRef();}
Note: We must always remember to make the necessary imports when creating any type of React Typescript component.
Form
component To the code written in the step above, we will add a form, which will be inside a render()
method containing an input field and a submit button. An onSubmit
event handler will be added to the form that is triggered after the text is entered in the input text field and is submitted after pressing the “Submit” button on the webpage. The code for this is shown below.
// Render method to define the component's UIrender() {return (<form className="commentForm" onSubmit={(e) => this.handleSubmit(e)}><h1>Using React Refs in TypeScript</h1><inputtype="text"placeholder="Enter text"ref={this.nameInputRef} // Bind the ref to the name input element/><button type="submit">Submit</button>{/* Extra features present: Display the entered text if there is a name in the state */}{this.state.name && (<div className="text"><p>Entered text: </p>{this.state.name}<div>{/* Rendering a button to refresh the page */}<button className="re" onClick={() => this.handleRefresh()}>Refresh</button></div></div>)}</form>);}
By using this render()
method, all DOM manipulations can be done via the handleSubmit()
method that takes the event object as a parameter of the React.FormEvent
type. Here, React refs come in where DOM elements are accessed with the ref name, this.nameInputRef
. It finds the actual DOM element associated with the ref and updates the form components’ state as a result. The code to implement this is shown below.
handleSubmit(e: FormEvent) {// Prevent the default form submission behaviore.preventDefault();// Check if the name input element exists in the refif (this.nameInputRef.current) {// Get the value of the input elementconst inputValue = this.nameInputRef.current.value;// Log the input value to the consoleconsole.log(inputValue);// Update the component's state with the input valuethis.setState({name: inputValue,});}}
To make sure the added functionality in our render()
function works, we will implement a simple handleRefresh()
event handler function that is triggered when the “Refresh” button appears on the webpage when we enter some text.
// Extra event handler function for refreshing the page as a part of additional functionality of the formhandleRefresh(){// Reload the current windowwindow.location.reload()}
After combining these functions, the Form.tsx
file will look as follows:
import React, { Component, FormEvent, RefObject } from 'react';//form.css file imported for extra webpage stylingimport "./form.css"//the interfaces states and properties specify the additional properties that the form component accepts// Define an interface for the component's stateinterface States {name: string;}// Define an empty interface for the component's propertiesinterface Properties {}// Create a class component named "Form" that extends the "Component" classexport class Form extends Component<Properties, States> {// Declare a private field to hold a reference to the name input elementprivate nameInputRef: RefObject<HTMLInputElement>;// superclass constructor function that takes "props" as an argument which is the constructor of the parent classconstructor(props: Properties) {// Call the constructor of the parent class "Component" with the "props"super(props);// Initialize the component's state with an empty "name" propertythis.state = {name: '',};// Create a ref for the name input elementthis.nameInputRef = React.createRef();}handleSubmit(e: FormEvent) {// Prevent the default form submission behaviore.preventDefault();// Check if the name input element exists in the refif (this.nameInputRef.current) {// Get the value of the input elementconst inputValue = this.nameInputRef.current.value;// Log the input value to the consoleconsole.log(inputValue);// Update the component's state with the input valuethis.setState({name: inputValue,});}}// Extra event handler function for refreshing the page as a part of additional functionality of the formhandleRefresh(){// Reload the current windowwindow.location.reload()}// Render method to define the component's UIrender() {return (<form className="commentForm" onSubmit={(e) => this.handleSubmit(e)}><h1>Using React Refs in TypeScript</h1><inputtype="text"placeholder="Enter text"ref={this.nameInputRef} // Bind the ref to the name input element/><button type="submit">Submit</button>{/* Extra features present: Display the entered text if there is a name in the state */}{this.state.name && (<div className="text"><p>Entered text: </p>{this.state.name}<div>{/* Rendering a button to refresh the page */}<button className="re" onClick={() => this.handleRefresh()}>Refresh</button></div></div>)}</form>);}}
Finally, we will include the form component inside our App.tsx
file present in the /src
directory to prevent any import errors at runtime. The code for this is shown below.
import React from 'react';import './App.css';import { Form } from './Form';//function that includes the form component inside <div> tagsfunction App() {return (<div className="App"><Form/></div>);}export default App;
We will then start the application with the npm start
command in the terminal.
All of the aforementioned steps can be seen within the code application below. Run it to see the app in action!
import React, { Component, FormEvent, RefObject } from 'react'; import "./form.css" //the interfaces states and properties specify the additional properties that the form component accepts // Define an interface for the component's state interface States { name: string; } // Define an empty interface for the component's properties interface Properties {} // Create a class component named "Form" that extends the "Component" class export class Form extends Component<Properties, States> { // Declare a private field to hold a reference to the name input element private nameInputRef: RefObject<HTMLInputElement>; // superclass constructor function that takes "props" as an argument which is the constructor of the parent class constructor(props: Properties) { // Call the constructor of the parent class "Component" with the "props" super(props); // Initialize the component's state with an empty "name" property this.state = { name: '', }; // Create a ref for the name input element this.nameInputRef = React.createRef(); } handleSubmit(e: FormEvent) { // Prevent the default form submission behavior e.preventDefault(); // Check if the name input element exists in the ref if (this.nameInputRef.current) { // Get the value of the input element const inputValue = this.nameInputRef.current.value; // Log the input value to the console console.log(inputValue); // Update the component's state with the input value this.setState({ name: inputValue, }); } } // Extra event handler function for refreshing the page as a part of additional functionality of the form handleRefresh(){ // Reload the current window window.location.reload() } // Render method to define the component's UI render() { return ( <form className="commentForm" onSubmit={(e) => this.handleSubmit(e)}> <h1>Using React Refs in TypeScript</h1> <input type="text" placeholder="Enter text" ref={this.nameInputRef} // Bind the ref to the name input element /> <button type="submit">Submit</button> {/* Extra features present: Display the entered text if there is a name in the state */} {this.state.name && ( <div className="text"> <p>Entered text: </p>{this.state.name} <div> {/* Rendering a button to refresh the page */} <button className="re" onClick={() => this.handleRefresh()}>Refresh</button> </div> </div> )} </form> ); } }
Note: Another way of verifying that the entered text was returned at the output is by going into the console after right-clicking and selecting the
inspect
option.
Using React refs in React TypeScript applications allows us to interact with the DOM elements in React components in a safer and more controlled manner, providing better type checking than its predecessor, the React.findDOMNode()
method. This method had been considered a performance bottleneck because it often bypassed React’s
Free Resources