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.