Exploring Refs in React

3 Min Read — In React

I came across Refs many times while browsing libs and docs but didn’t really care about it. My idea of Refs was limited to controlling <input /> in forms. Then I came across a piece of code in which user was able to control 3rd party component using Refs. This was totally different way of writing React. That is when I decided to explore Refs

Declarative Way of React

React components are independent of each other

In below example, only way of controlling behaviour of <Child /> from <Parent />is by passings props to it.<Child /> is designed to get desired output depending on the props passed.

class Parent extends Component {
render() {
return (
<Child name='John' />
)
}
}
class Child extends Component {
render() {
const { name } = this.props;
if(name){
return(
<p>Hello {name}</p>
)
}
return (
<p>Hello World</p>
)
}
}

The above case is possible because we were able to edit <Child />. But what if <Child /> is a 3rd party library and we are not able to edit it.

Refs to rescue

Refs provide a way to access DOM nodes or React elements created in the render method.

Basically it means any element that is rendered in render function can be accessed using Refs.

The Imperative Solution

Lets modify above example, here we will try to access non editable <Child />from<Parent />

class Parent extends Component {
myRef = React.createRef();
render() {
return (
<Child ref={this.myRef} />
)
}
}

To accomplish this, first we create Refs using React.createRef() and stored it in myRef variable

Then we attach myRef with <Child /> using ref attribute. ref is special attribute just as we have key attribute and it is not passed down as prop.

So what exactly attaching means?

When <Parent />* *is rendered, React will assign instance of <Child />to current attribute of myRef variable. Inspecting myRef.current returns a object

console.log(myRef.current)

With* myRef.current we *can access props, state, can setState and forceUpdate the <Child /> from <Parent />. Also functions defined on <Child /> can be accessed.

Here we are updating <Child />on some click event in <Parent />

handleClick(){
this.myRef.current.setState({ ... })
}

Real World Ex:

ReactSlick: Refs are used to customise slider feature provided by react-slick. In below example we want to change slides of slider on change in input. So we attach<Slider ref={this.slider} /> with a Refs. And onChange in input, we call internal function of slider this.slide.slickToGo to change slides

class SlickGoto extends Component {
slider = React.createRef();
....
render() {
return (
<div>
<input
onChange={e => this.slider.slickGoto(e.target.value)}
type="range"
/>
<Silder ref={this.slider} {...settings }>
...
</Slider>
</div>
)
}
}

Ref is limited to class and html elements:

Refs are limited to class component and html elements. Assigning to html is similar as we have shown for class component.

class MyText extends Component {
myRef = React.createRef();
componentDidMount(){
this.myRef.current.style.background = 'red'
}
render() {
return (
<h1 ref={this.myRef}>Yoo</h1>
)
}
}

In case of html element, inspecting myRef.current will return DOM node. As shown above we can call DOM functions on myRef.current

Refs cannot be attached on functional component. But they can be used inside of functional component. As long as attached element is html or class Refs will work.

myRef1 = React.createRef();
function Child(){
return <h1 ref={this.myRef1}>Helo!</h1>
}
class Parent extends Component {
myRef2 = React.createRef();
render() {
return (
<Child ref={this.myRef2} />
)
}
}

In above case myRef1 will return instance of <h1 />and myRef2 will return null as it is attached to functional component

Hooks

You might have heard about Hooks. useRef is the API built on top of Hooks. It is similar to createRef. Difference is if createRef is used in functional component it creates Refs every-time the function is re-rendered. While in case of useRef it get initialised only once. This issue is avoided in class component by defining Refs in constructor itself.

Passing refs as props:

Refs can be passed as any regular props. In this example Refs created in <GrandParent /> is passed to <Parent /> as prop. Then it is assigned to <Child /> in render function of <Parent />

class GrandParent extends Component {
myRef = React.createRef();
someFunction(){
this.myRef.current.setState({ ... })
}
render() {
return (
<Parent anyName={this.myRef} />
)
}
}
class Parent extends Component {
render() {
return (
<Child ref={this.props.anyName} />
)
}
}

<GrandParent /> can now control behaviour of <Child /> using this.myRef

Refs with High Order Components(HOC):

Consider we write some HOC and us it while exporting <Child />

function hoc(WrappedComponent){
class HOC extends Component {
...
...
render(){
return <WrappedComponent {...this.props} />
}
}
return HOC
}
class Child extends Component {
...
}
export default hoc(Child)

Now here we expect that, when myRef.current is inspected it should return instance of <Child /> but we get instance of <HOC />

import Child from "./child"
class Parent extends Component{
myRef = React.createRef();
render() {
return (
<Child ref={this.myRef} />
)
}
}

One way of handling this is by passing down ref as prop as we did above in <GrandParent /> example. But again life as they say is not simple and we wrote this <HOC /> in some lib and as you might have guessed we can’t edit the code.

Forward Ref

React introduced new feature called forwardRef to solve problem like this. Remember I told you ref is special attribute and values in ref are not available as prop, well forwardRef changes that.

function hoc(WrappedComponent){
class HOC extends Component {
...
...
render(){
cosnt { forwardRef, ...rest } = this.props;
return <WrappedComponent ref={forwardRef} {...this.props} />
}
}
return React.forwardRef((props, ref) => {
return(
<HOC {...props} forwardRef={ref} />
)
})
}
class Child extends Component {
...
}
export default hoc(Child)

We make few changes to HOC fn , instead of directly returning <HOC />, we return it using forwardRef as shown here.

The ref passed to <Child /> is received as second parameter to forwardRef function. Second parameter ref is again passed down as prop to <HOC /> and finally it is attached to <WrappedComponent />.

In our case <WrappedComponent />is <Child /> and hence myRef in <Parent /> will return instance of <Child />

Conclusion

I have been working on React for long time but I never used Refs. The application of Refs are very limited. One handy rule to decide whether to use Refs or not, is to check if we are able to edit the component that we are trying to access. If not then go for Refs else declarative style is the best way to go.

According to React Docs and I agree

Avoid using refs for anything that can be done declaratively.