skip to Main Content

I have encountered a scenario where I need to pass the ref of an element in child to the parent. I could not do this using the "forwardRef" approach as I render a list of children in the parent component.

I have passed the ref from the child as a method parameter to one of the parent method which is accessible via props. This works and I was able to access the ref in the parent to achieve what I wanted.

I want to know if this approach would result in any future problem or if it is an anti-pattern. Please let me know if the same can be achieved through any other means.

Below is a sample code demonstrating my implementation.


export const Child: React.FC<any> = (props: any) => {

  const childRef = useRef<any>(null);

  const clickHandler = () => {
    props.onListItemClick(childRef);
  };

  return (
    <li onClick={clickHandler} ref={childRef}>
      {props.key} - {props.item.name}
    </li>
  );
};

export const Parent: React.FC<any> = (props: any) => {

  const listItemClickHandler = (clickedChildRef: any) => {
    // use the child ref to set the scroll
    if (clickedChildRef && clickedChildRef.current) {
      clickedChildRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  };

  return (
    <ol>
      {props.data.map((item: any, index: number) =>
        <Child item={item} key={index} onListItemClick={listItemClickHandler} />
      )}
    </ol>
  )
};

2

Answers


  1. I want to know if this approach would result in any future problem or
    if it is an anti-pattern. Please let me know if the same can be
    achieved through any other means.

    Your approach might work, not sure. But there is official example for working with list of refs (here I have adapted it for custom components):

    import React from 'react';
    import './style.css';
    
    const MyComponent = React.forwardRef(function MyInput(props, ref) {
      React.useImperativeHandle(
        ref,
        () => {
          return {
            test: () => {
              console.log('someProp', props.someProp);
            },
          };
        },
        []
      );
    
      return <div>Hello</div>;
    });
    
    export default function App() {
      const itemsRef = React.useRef(null);
    
      function getMap() {
        if (!itemsRef.current) {
          // Initialize the Map on first usage.
          itemsRef.current = new Map();
        }
        return itemsRef.current;
      }
    
      return (
        <>
          <div>
            <button
              onClick={() => {
                itemsRef.current.get('3').test();
              }}
            >
              Call the test method for the third item
            </button>
            <ul>
              {items.map((item) => (
                <MyComponent
                  key={item.id}
                  someProp={item.id}
                  ref={(node) => {
                    const map = getMap();
                    if (node) {
                      map.set(item.id, node);
                    } else {
                      map.delete(item.id);
                    }
                  }}
                />
              ))}
            </ul>
          </div>
        </>
      );
    }
    
    const items = [];
    for (let i = 0; i < 10; i++) {
      items.push({
        id: i.toString(),
      });
    }
    
    Login or Signup to reply.
  2. For posterity, useRef here is superfluous, the two snippets are equivalent in functionality:

    export const Child: React.FC<any> = (props: any) => {
    
      const childRef = useRef<any>(null);
    
      const clickHandler = () => {
        props.onListItemClick(childRef);
      };
    
      return (
        <li onClick={clickHandler} ref={childRef}>
          {props.key} - {props.item.name}
        </li>
      );
    };
    

    When you could just do this:

    export const Child: React.FC<any> = (props: any) => {
    
      const clickHandler = (e) => {
        // although you wouldn't need to pass it as an object with key current
        // it's just an example to show that useRef isn't needed
        props.onListItemClick({current:e.target});
      };
    
      return (
        <li onClick={clickHandler}>
          {props.key} - {props.item.name}
        </li>
      );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search