Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: how to forward refs to multiple children?

Tags:

reactjs

I'm trying to forward multiple refs to multiple children DOM nodes:
I need references to the 7 buttons, so I can manage focus between them.
I tried by using an array of React.createRef() and then attach each element of this array to a child using index, but all refs refers to the last button.
Why and is there another solution?

class RestaurantsList extends Component {
  references = Array(7).fill(React.createRef());

  render() {
    return (
      <ul 
        id="restaurants-list"
        role="menu"
      >
        {
          this.props.restaurants.map((restaurant, index) => {
            return (
              <Restaurant 
                ref={this.references[index]}
                name={restaurant.name}
              />
            );
          })
        }
      </ul>
    );
  }
}

const Restaurant = React.forwardRef((props, ref) => {
  return (
    <li>
      <button 
        ref={ref}
      >
        {name}
      </button>
    </li>
  );
})
like image 448
Moaaz Bhnas Avatar asked Dec 11 '18 06:12

Moaaz Bhnas


People also ask

How do I forward multiple refs React?

To forward multiple refs with React, we can pass in the refs in an object. We have the Child component that accepts refs since we created it by calling forwardRef with the component function. In the function, we destructure the refs we pass in by using: const { ref1, ref2 } = ref.

How do you use forwarded REF IN React?

The only way to pass a ref to a function component is using forwardRef. When using forwardRef, you can simply pass the ref to a DOM element, so the parent can access it like in example 1, or you could create an object with fields and methods using the useImperativeHandle hook, which would be similar to eample 2.

How do you get ref for kids React?

In child component, we create Refs by using React. createRef() and then attached to React elements via the ref attribute. // EXPLANATION: a reference to the node becomes accessible at the current attribute of the ref. In the parent component, we can get a reference to the Input component and call its focus() method.


1 Answers

As it was discussed in comments the best way is to keep list of references as an array or object property inside parent component.

As for Array.prototype.fill() its arguments is calculated just once. In other words fill(React.createRef()) will generate list where each entry will refer to the same object - and you will get equal ref to last element. So you need to use .map() for getting unique reference objects.

references = Array(7).fill(0).map(() => React.createRef());

Anyway in real world project this will rather happen in constructor() or componentDidUpdate().

But I believe it's better to have hashmap:

references = {};

getOrCreateRef(id) {
    if (!this.references.hasOwnProperty(id)) {
        this.references[id] = React.createRef();
    }
    return this.references[id];
}

render() {
    return (
    ....
    {
      this.props.restaurants.map((restaurant, index) => {
        return (
          <Restaurant 
            ref={this.getOrCreateRef(restaurant.id)}
            key={restaurant.id}
            name={restaurant.name}
          />
        );
      })
    }    

Also you will need some helper methods to avoid exposing this.references to outer world:

focusById(id) {
    this.references[id].current && this.references[id].current.focus();
}

And take special attention to cleaning up references to unmounted elements. Otherwise you may got memory leak if list of restaurants is changed dynamically(if ref stays in this.references it keeps reference to HTML element even if it has been detached). Actual need depends on how is your component used. Also this memory leakage will be fixed once container(that has reference = {}) is unmounted itself due to navigating away.

like image 132
skyboyer Avatar answered Apr 06 '23 01:04

skyboyer