Refs and Forward Refs in React.

Akhilesh Ojha
The Startup
Published in
5 min readSep 13, 2020

React Refs are a useful feature that acts as a means to reference a DOM element.

But the question here is why can we not directly manipulate the DOM using normal JavaScript DOM Manipulation?

Here is an example of why normal JS DOM Manipulation won’t be as useful as Refs when it comes to using React.

Suppose you have a component which has multiple input element. Now, what if we want to focus on the second input element or any other input element as soon as we render the component?

What happens when we use normal JS DOM Manipulation?

import React, { Component } from 'react';export class InputRef extends Component {componentDidMount() {
document.querySelector('input').focus();
}
render() {
return (
<div>
<input type="text" />
<input type="text" />
<input type="text" />
</div>
)
}
}
export default InputRef;

Here we do indeed focus, but we always focus on the first input element. Simply because document.querySelector selects the first input element it finds in the entire DOM.
This is because document.querySelector is not something that is related to React but is a general DOM Selector.

We can anyway add an id to these inputs and focus according to id, but React offers a better alternative. The Refs.

Refs in Class-Based Components. Old Approach.

Here we pass a function (anonymous arrow function)in the input element which you want to focus on. The argument that we get is a reference to the element on which we place the function and we can use that reference in our function body.

import React, { Component } from 'react';export class InputRef extends Component {componentDidMount() {
this.inputElRef.focus();
}
render() {
return (
<div>
<input type="text" />
<input type="text" ref={(inputElRef) => {this.inputElRef = inputElRef}}/>
<input type="text" />
</div>
)
}
}
export default InputRef;`

Here this.inputElRef will store our reference in global property and hence we can access that reference anywhere in our component (in our case componetDidMount()).

Since React-16.3 we now have another way of using Ref and that uses CreateRef.

Here instead of passing a function to Ref, we can directly assign the value that we have using createRef to our input element.

import React, { Component } from 'react';export class InputRef extends Component {constructor(props) {
super(props);
this.inputElRef = React.createRef();
}
componentDidMount() {
this.inputElRef.current.focus();
}
render() {
return (
<div>
<input type="text" />
<input type="text" ref={inputElRef}/>
<input type="text" />
</div>
)
}
}
export default InputRef;`

Just a side note here. We will have to access the ref using the current property which gives us access to current reference.

Refs with react hooks

To use Refs in React hooks we have a hook provided by React — useRef.

import React , { useEffect ,  useRef } from 'react'const inputRef = (props) => { const inputElRef = useRef(null);
useEffect(() => {
inputElRef.current.focus();
}, [])
return (
<div>
<input type="text" />
<input type="text" ref={inputElRef}/>
<input type="text" />
</div>
)
}
export default inputRef;

Side Note: We can pass an initial value in useRef to store any value, hence useRef is not just used to get access for the DOM element but we can store any other values as well. For this example, however, we pass null as an initial value.

Now as we know how to create a ref we can talk about how to Forward a Ref.

Forwarding ref is a technique for automatically passing a ref through a component to one of its child components.

I’ll try to explain what forward Ref does through a basic use case.

Suppose we have a Navbar component and we have a Home Component.
Our Home Component has two sections viz “Our Services”, “Contact-Us”.
Navbar Component will have links to these Sections which when clicked will scroll us to that particular Section.

Now an easy and direct way for this is to pass the id of a section in href directly.

<a href={"#serviceId"} >Our Services</a>

But to give you an example on how Forward ref work let’s pass on the id from Home Component to NavBar Component via our Parent Component. Looks a bit convoluting but it is damn easy!! :)

Now the typical React Structure will look something like this.
We will have a parent Layout/App Component which will have a component of Navbar and Home. (i.e Home and NavBar are children to Layout Component)

Now, this is how we will forward Refs:

Step 1: Create a ref for the Home Section in Parent Component (Layout). Pass all the refs as a single object.

import React, { Component } from 'react';
import './App.css';
import NavBar from './components/NavBar/NavBar';
import Home from './components/Home/Home';
class App extends Component {constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
}
render() {
return (
<div className="App">
<NavBar />
<Home ref={{homeRefService: this.homeRefService, homeRefContact: this.homeRefContact }}/>
</div>
);
}

}
export default App;

Step 2: Forward this ref in Home Component using the dot forwardRef method from React Library.

The forward Ref method takes a component as its parameter. So we will have to pass the functional Home component as a parameter to forwardRef. Now every component receives “props ” as its parameter, but when we use forwardRef method, we can pass “ref” as a second parameter for that component.
It will be clear when we see the below code for Home Component.

import React from 'react';const home = React.forwardRef((props , ref) => {
console.log('Ref', ref);
return (
<div>
<section>
Our Services
</section>
<section>
Contact Us
</section>
</div>
)
})
export default home

The output of console.log right now will be :

{homeRefContact: {current: null}
homeRefService: {current: null}}

We can then use Object Destructuring to get the refs for each section respectively and we pass the corresponding refs to each section.

const { homeRefService , homeRefContact } = ref;<section ref={homeRefService} id="Services"> Our Services </section>
<section ref={homeRefContact}id="Contact"> Contact Us </section>

Once we pass these refs in each section on our Home Component, we will now have reference to these in our Parent Component (App / Layout).

Step 3: Since now we have the reference of sections present in the Home Component in our App Component (because of forward ref) we set the ref in state of App Component and then pass these refs as props to the NavBar component. Below is the code for App.js.

constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
this.state = { homeServiceId: "", homeContactId: "" }
}
componentDidMount() {
this.setState({
homeServiceId: this.homeRefService.current.id,
homeContactId: this.homeRefContact.current.id
});
}
render() {
return (
<div className="App">
<NavBar homeServiceId={this.state.homeServiceId} homeContactId={this.state.homeContactId}/>
<Home ref={{homeRefService: this.homeRefService, homeRefContact: this.homeRefContact }}/>
</div>
);
}

A side note here:

The ref is only accessible when the component gets mounted to the DOM. So we will have to access the DOM element in componentDidMount and hence we need to maintain a state in App.js for these refs.

Step 4: Finally we want to access the props in our NavBar component to get the id for these refs.

export class NavBar extends Component {
render() {
return (
<div>
<a href={'#' + this.props.homeServiceId}> Our Services</a>
<a href={'#' + this.props.homeContactId}>Contact Us</a>
</div>
)
}
}

If you need to implement this functionality, you can add a CSS property of
scroll-behavior: smooth; and provide height to each section to check the scroll behavior.

Conclusion

We had a basic understanding of how ref actually works and how can we use forward ref. Using refs will definitely make our React code better as we will be more decisive in how we manage our state, props, and re-rendering. To read more about refs check out the docs here.

--

--