Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is React ref Order guaranteed?

I'm constructing an element with an unknown number of children using React and I need a reference to each. Consider the following code:

class Items extends React.Component {
    constructor() {
        super();
        this.itemEls = [];
    }

    render() {
        return (
            <div>
                {
                    this.props.items.map((item, index) => {
                        return <div className="item" key={index} ref={(el) => this.itemEls.push(el)}>{item}</div>;
                    })
                }
            </div>
        );
    }

    componentDidMount() {
        console.log(this.itemEls.map((el) => el.innerHTML));
    }
}

ReactDOM.render(<Items items={["Hello", "World", "Foo", "Bar"]} />, document.getElementById('container'));
.item {
  display: inline-block;
  background: #EEE;
  border: 1px solid #DDD;
  color: #999;
  padding: 10px;
  font-family: sans-serif;
  font-weight: bold;
  margin: 10px;
  border-radius: 5px;
  cursor: default;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="container"></div>

I loop through the provided array and save a reference to each element as I go. When the component mounts, I print out a list of the element contents in the order they were added to the array. So far in all of my testing the order has been correct, but I can't find any documentation that confirms that this isn't just the most common result of a secret race condition going on in the background.

Is the order in which reference functions are called consistent?

The obvious solution here would be to pre-set the array length in componentWillMount and populate it with the index value from this.props.items.map - but for the sake of argument, let's say that's not possible in the actual situation I find myself in.

like image 888
Sandy Gifford Avatar asked Jan 20 '17 21:01

Sandy Gifford


1 Answers

The documentation says:

The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.

The components mounted using map are obviously mounted in the correct order, so I guess the ref callbacks are executed in order. If you suspect that the ref callbacks may be async I think you can verify this assumption by throwing an error from the callback - if the execution stops at first element, then it's synchronous (please someone correct me if I'm wrong).

If you're still concerned you can add the elements to array by the item index instead of push (which is probably what I'd do anyway):

this.props.items.map((item, index) => {
   return <div className="item" key={index} ref={(el) => {this.itemEls[index] = el} }>{item}</div>;
 })

This way you'll be protected against empty items which are skipped over by map, for example:

let a = [];
let b = [];
let items = ['Hello', 'World', 'Foo', , ,'Bar']; // items.length == 6
delete items[1];
items.map( i => { a.push( i ); return i });
console.log( a ); // [ "Hello", "Foo", "Bar" ] 
                  // - the index of "Foo" and "Bar" doesn't match original index
items.map( (i, idx) => { b[idx] = i; return i });
console.log( b ); // [ "Hello", <empty>, "Foo", <empty>, <empty>, "Bar" ]
                  // sparse array, but matching indexes
like image 123
pawel Avatar answered Oct 12 '22 07:10

pawel