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>
);
})
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With