React Refs for Function Component

React Refs for Function Component

  • 1471

React components represent DOM tags that make up a particular part of a UI returned by the `render` method. For cases where you want to interact with the DOM node outside the regular React lifecycle, we can use refs.

React components represent DOM tags that make up a particular part of a UI returned by the render method. For cases where you want to interact with the DOM node outside the regular React lifecycle, we can use refs.

In this post, we will go over a short example where we need to dynamically reference the DOM node in functional child components by demonstrating how useRef and createRef differ.

Create the ref object using useRef

_useRef_ returns a mutable ref object whose _.current_ property is initialized to the passed argument (_initialValue_). The returned object will persist for the full lifetime of the component. — React Docs

Let’s declare a parent-child relationship where a list component renders some undetermined number of rows:

// Parent: Feed
// Child: FancyRow 
function Feed({ edges }: FeedProps): React.Element<'div'> {
  const parentRef = useRef<HTMLDivElement>();
  return (
    <div className={styles[‘feed’]}>
      <h4 className={styles[‘feed__item-title’]}>
         List Container
      </h4>
      {edges.map((edge, i) => (
      <FancyRow key={i} />
      ))}
    </div>
  );
}

React treats the refattribute similar to the key prop when making lists. Declaring the ref attribute on a function component will trigger an error in the console.

/* Error, ref will be undefined */
{edges.map((edge, i) => (
   <FancyRow ref={parentRef} key={i} />
))}

To understand why this happens, recall React classes are objects that create instances to hold an object’s state.

Function components, on the other hand, do not have instances. When function components are re-rendered, the refObject will lose its reference to the DOM and undefined.

Alternate props

We can reference our DOM from the child by declaring an alternative accessory from the parent like the following:

// Parent
{edges.map((edge, i) => (
   <FeedRow parentRef={parentRef} key={i} />
))}
// Child 
function FeedRow(props: RowProps): React.Element<’div’>{
 return (
    <div ref={props.parentRef} className={styles[‘feed__item’]} >
       <div className={styles[‘feed__item-meta’]}>
         <button>Container Button</button>
       </div>
    </div>
 );
};

Alternatively, according to the docs, we can “forward” the ref directly to a child component using forwardRef. We can use the regular ref attribute and get the reference to one of the children from the component wrapped inforwardRef as regular props.

// Parent
{edges.map((edge, i) => (
   <FancyRow ref={parentRef} key={i} />
))}
const FancyRow = React.forwardRef((props, ref) => 
 <FeedRow 
   key={props.edge} 
   parentRef={ref} 
   {…props} 
 />
// Child 
function FeedRow(props: RowProps): React.Element<’div’>{
 return (
    <div ref={props.parentRef} className={styles[‘feed__item’]} >
       <div className={styles[‘feed__item-meta’]}>
         <button>Container Button</button>
       </div>
    </div>
 );
};

So far, we have a list of row containers.

This is image title

Let’s look further into how the useRef hook references the DOM rendered from a child. Here, we create a click handler from the parent and pass it to the child as regular props.

function Feed({ edges }: Props): ReactNode {
 const parentRef = React.useRef();
const onClick = () => {
   if (parentRef.current) {
    parentRef.current.style.border = ‘5px dashed red’;
   }
 };
return (
   <div className={styles[‘feed’]}>
    <h4 className={styles[‘feed__item-title’]}>List Container</h4>
    {edges.map((edge, i) => (
      <FancyRow
       ref={parentRef}
       key={i}
       parentOnClick={onClick}
       edge={edge}/>))}
    </div>
 );
}

We will also want to update FeedRow and attach our click handler from props as the event handler for the button.

const FeedRow = (props): React.Element<’div’> => {
 return (
  <div ref={props.parentRef} className={styles[‘feed__item’]} >
   <div className={styles[‘feed__item-meta’]}>
     <button onClick={() => props.parentOnClick()}>
       Container Button
     </button>
   </div>
  </div>
 );
};

If we click any button from our list, only the final container will render a dashed, red border. As the docs state, the object returned by will persist for the full lifetime of the component. Our reference to the DOM node of FeedRow is set once in the lifecycle of Feed as it renders its children elements.

This is image title

Create the ref object using createRef

In contrast to useRef, createRef does not preserve the ref instance to the DOM in a child and will always create a new reference.

In applications where children are dynamically rendered based on data, we want to assign new ref objects for each child element on the initial render. We can store a list of refs in the local state of Feed

const [elements] = useState<Array<HTMLDivElement>>(
    Array(edges.length)
    .fill(0)
    .map(() => React.createRefuseState<Array<?HTMLDivElement>>())
);

Revisiting our top-level parent component, we pass our reference array an index value as we map over our data.

// Parent
const onClick = (rowIndex: number): void => {
    if (elements[rowIndex]) {
      elements[rowIndex].current.style.border = '5px dashed red';
    }
};
...
{edges.map((edge, i) => (
    <FancyRow
       rowIndex={i}
       ref={elements[i]}
       key={i}
       parentOnClick={onClick}
       edge={edge}>
    </FancyRow>))}

And by threading the index prop from the child component as a parameter to the click handler function, we can get the correct DOM reference:

const FeedRow = (props): Element<’div’> => {
 return (
    <div ref={props.parentRef} className={styles[‘feed__item’]} >
      <div className={styles[‘feed__item-meta’]}>
       <button onClick={() =>                  
           props.parentOnClick(props.rowIndex)}>
          Container Button</button>
      </div>
    </div>);
};

And there you have it. Use this pattern to dynamically assign refs to children in order to reference their DOM nodes.

This is image title

Thanks for reading!