Exploring Refs 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
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><inputonChange={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.