What is a ref?
A ref in react is an escape hatch from regular flow so that you can directly access the DOM node or rendered react component.
We can define the ref using this syntax:
const ref = React.creatRef()
When we have a ref, we normally pass it to a DOM element or React component. The react component in which we are passing the ref must be only a class component. It can never be a functional component otherwise react will throw an error.
We pass ref like this:
in DOM node
<div ref={ref}>I am div element</div>
in Class Component
<ClassComponent ref={ref} />
Once ref is passed, after the mounting of the component, that ref will point to that DOM Node in case of DOM element (you can access all of its methods and properties) or will point to the mounted state of that component in case of class component.
You can access the ref using current
property.
const node = ref.currentconsole.log(node) // logs mounted state of component
Benefit of ref:
- It provides a way to do the tasks which are considered an escape hatch such as accessing or focusing on the DOM node and doing the work without re-render, starting animations etc without rendering the DOM.
- It helps in properly integrating third parties DOM library to the react application.
The drawback of ref:
It was evident that using ref in the react application is different from regular react flow as we know it from an earlier discussion. It is an escape hatch to do the task which otherwise would be too much difficult to do or have extra complexity. Overusing of ref in the project might increase complexity. So it is advisable to not overuse ref although first-time intuition would be to do everything with using ref.
There are some other drawbacks also:
- ref is the mutable property and it can be changed from any place where
current
property is accessible. You will need to use it carefully. - Once the ref is changed, it doesn’t re-render the component which might be the bottleneck for you.
Ref in react:
When we create a ref, we pass it to either the DOM node in the current component or its child component. This flow is very intuitive because the same way we pass the props down to the child and that child may pass it to its child.
Let’s see the below example for more understanding:
Example 1:
Parent.js
import React, { useEffect } from "react"import Child from "./Child"const Parent = () => {const ref = React.createRef()useEffect(() => {const node = ref.currentconsole.log(node)}, [])return <Child ref={ref} />}export default Parent
Child.js
import React from "react"class Child extends React.Component {render() {return <div>I am child component</div>}}export default Child
In the console, we will get the reference of the mounted child component as logged
And if Child has one child named grandchild and we want to pass the ref from Parent to GrandChild, we can see that in the below example. For passing the ref from Parent to GrandChild, we will use a react feature which is called ref forwarding meaning passing the ref to the component below.
Example 2:
Parent.js
import React, { useEffect } from "react"import Child from "./Child"const Parent = () => {const ref = React.createRef()useEffect(() => {const node = ref.currentconsole.log(node)}, [])return <Child ref={ref} />}export default Parent
Child.js
import React from "react"import GrandChild from "./GrandChild"class Child extends React.Component {render() {const { forwardedRef, ...rest } = this.propsreturn <GrandChild ref={forwardedRef} {...rest} />}}export default React.forwardRef((props, ref) => {return <Child forwardedRef={ref} {...props} />})
or (If we want to rewrite the Child component as a functional component)
import React from "react"import GrandChild from "./GrandChild"const Child = React.forwardRef((props, ref) => {return <GrandChild ref={ref} {...props} />})export default Child
GrandChild.js
import React from "react"class GrandChild extends React.Component {render() {return <div>I am grandchild component</div>}}export default GrandChild
as you can see from the above example, the ref is flowing down (like a top-down approach).
Most of the time we should follow the above approach (top-down approach) but sometimes it is a necessity to send the ref in reverse order meaning that if you have defined the ref or created the ref in the child component and you want to send it to its parent. That is also possible but with few restrictions
Access ref from different component
Here I am not referring to the normal flow (top-down approach ). I am talking about accessing ref by its parent component or by its sibling component.
We can send the ref from child to parent and through that, it's a sibling component but there is one condition → The component in which we are creating the ref, must be a CLASS COMPONENT. This approach I tested this during the writing of this blog.
Access ref from parent component:
We can access ref from the parent by sending ref as function props, we will send it as props as soon as the component is mounted.
Child.js
import React from "react"class Child extends React.Component {constructor(props) {super(props)this.ref = React.createRef()}componentDidMount() {this.props.refFromChild(this.ref)}render() {return <input ref={this.ref} />}}export default Child
Parent.js
import React, { useState } from "react"import Child from "./Child"const Parent = () => {const [childRef, setChildRef] = useState(null)const handleRef = ref => {setChildRef(ref)}const handleClick = () => {if (childRef) {childRef.current.focus()}}return (<><button onClick={handleClick}>Click</button><Child refFromChild={handleRef} /></>)}export default Parent
As you can see in the above example, the ref is passed to the parent from the child using refFromChild
function props and it gets stored in childRef
state which can be used later to perform some kind of task later such as trigger focus on input element from the parent component.
Access ref from sibling component:
Sibling components are those components whose parent is the same.
As you can see in the above pic, Child1 and Child2 components have the same parent component (Parent1), thus these two are sibling components similarly Child3 and Child4 also have some parent (Parent2), thus these two (Child3 and Child4) are sibling components.
When we want to access ref from the sibling component, we first need to pass ref to parent from the child (in which ref is defined) and do ref forwarding to the sibling component.
Let's see that in the below example:
Child.js
import React from "react"class Child extends React.Component {constructor(props) {super(props)this.ref = React.createRef()}componentDidMount() {this.props.refFromChild(this.ref)}render() {return <input ref={this.ref} />}}export default Child
Parent.js
import React, { useState } from "react"import Child from "./Child"import Sibling from "./Sibling"const Parent = () => {const [childRef, setChildRef] = useState(null)const handleRef = ref => {setChildRef(ref)}return (<><Child refFromChild={handleRef} /><Sibling ref={childRef} /></>)}export default Parent
Sibling.js
import React from "react"const Sibling = React.forwardRef((props, ref) => {const handleClick = () => {if (ref) {ref.current.focus()}}return <button onClick={handleClick}>Click From Sibling</button>})export default Sibling
As you can see in the above example, the ref is created in the Child
component and then passed to the Parent
component using the refFromChild
method which set the state childRef
in the Parent
component. And then the ref is forwarded to the Sibling
component. Now if we click on the Click From Sibling
button, it will focus on the input element in the Child
component.
This is working…. wohooo…
This is all about how we can access the ref from different components (parent and sibling). Similarly, if you want to pass a ref to the multi-level parent or grandparent component or great-grandparent component, you just need to replicate the above approach. That will be super easy for you.
Thanks for reading the article. Keep coding and keep solving problems.