React: Array of references.

Created on 21 Jul 2014  路  14Comments  路  Source: facebook/react

E.g, when I generate list of buttons, I want to assign every button to same array, which is accessible through this.refs. After that, I can perform operation on every button easily. Something like square bracket syntax <input type="text" name="input[]" /> in html.

var ButtonList = React.createClass({
    render: function() {
        return (
            {this.props.buttons.map(function(button) {
                return <MyButton ref="buttons[]" onClick={this.handleClick} label={button.label} />;
            })}
        );
    },
    handleClick: function() {
        this.refs.buttons.forEach(function(button) {
            // perform operation on button
        });
    }
});

Most helpful comment

Inserting a full working example here for others' reference

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this._nodes = new Map();
    }
    componentDidMount() {
        this.checkNodes();
    }
    componentDidUpdate() {
        this.checkNodes();
    }
    checkNodes() {
        Array.from(this._nodes.values())
            .filter(node => node != null)
            .forEach(node => {
                // do something with node
            });
    }
    render() {
        const { values } = this.props;
        return (
             <div>
                 {values.map((value, i) => (
                     <div key={i} ref={c => this._nodes.set(i, c)}>{value}</div>
                 ))}
             </div>
        )
    }
}

All 14 comments

It's not currently possible, though you could do ref={'button' + i}, then access it through this.refs['button' + i].

:+1:

As well as giving each ref a unique name as @chenglou said, you can access all of the components refs with something like _.each(this.refs, function(item, name) { if (item.doSomething) item.doSomething(); }); if trying to call a method on each, or perhaps check if name begins with 'button' etc. I agree an array of references would also be useful though. We were just wishing for this too.

This will likely be more possible after #1373.

Going to close out to try open issues down (even if we're currently failing). Let's make it an explicit goal/non-goal of #1373.

@spicyj @zpao is it now possible, since #1373 has been closed? For example, how to support an array that may have a fluctuating number of elements?

@arcanis Yes, it is possible, setup a callback ref and save the ref to an array. For more info, check out the docs (https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) or ask a usage question on stack overflow :).

@jimfb I thought of that, but what if the number of elements shrinks? When should the extraneous elements be removed? And I asked here because it will be the first place people will find ("react array of refs" on Google) :)

@arcanis We use github issues to track bugs in the React core, and explicitly send usage questions to StackOverflow. We encourage cross-linking, so feel free to post a link to you SO question so future travelers can go to the same place.

To answer your question of when elements should be removed, the answer is "when the callback ref fires a null". You can create a closure to capture the name/identity/key in your callback function, and access/remove that value when the ref fires.

Inserting a full working example here for others' reference

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this._nodes = new Map();
    }
    componentDidMount() {
        this.checkNodes();
    }
    componentDidUpdate() {
        this.checkNodes();
    }
    checkNodes() {
        Array.from(this._nodes.values())
            .filter(node => node != null)
            .forEach(node => {
                // do something with node
            });
    }
    render() {
        const { values } = this.props;
        return (
             <div>
                 {values.map((value, i) => (
                     <div key={i} ref={c => this._nodes.set(i, c)}>{value}</div>
                 ))}
             </div>
        )
    }
}

@harmony7 I use your example and it works for filtering but what if I need use simulation of currentElement.click()
like https://facebook.github.io/react/docs/refs-and-the-dom.html

Like this?

(Trying to make a setup similar to https://facebook.github.io/react/docs/refs-and-the-dom.html#adding-a-ref-to-a-dom-element )

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this._nodes = new Map();
        this._handleClick = this.handleClick.bind(this);
    }
    handleClick(e, i) {
        // Explicitly focus the text input using the raw DOM API
        const node = this._nodes.get(i);
        node.focus();
    }
    render() {
        const { values } = this.props;
        return (
             <div>
                 {values.map((value, i) => (
                     <div key={i}>
                         <input type="text" 
                             value={value}
                             ref={c => this._nodes.set(i, c)} />
                         <input type="button"
                             value="Focus the text input"
                             onClick={e => this.handleClick(e, i)} />
                     </div>
                 ))}
             </div>
        )
    }
}

Just so you know, this example is for illustration of this concept only and it does not follow some best practices, for example it is probably not the best to create the inline arrow function to handle onClick on each render.

The inline arrow function is fine here.

for React 16+
and functional component, for class you can use this.domElements = []
const domElements = [];
arrayData.map((el) => (<button ref={(node) => domElements.push(node)} />))

@chenglou 's solution still works even today in 2019 (O_o) thanks.
I was mapping through an array to show tooltips using react-tooltip but the ref only always caught the last iteration. I solved it this way.
create array of refs: this.handleRefArray(itemData.key, itemIndex);
create ref: ref={refArray[itemIndex]}
accessed ref: afterShow={() => this.handleHideTooltipDelay(this.refs[refArray[itemIndex]])}

Was this page helpful?
0 / 5 - 0 ratings