Hasen Hasen - 6 months ago 19
iOS Question

React Native staggered render

With this code how would I make the render staggered? I want it to render each element one by one rather than just rendering all at once. I've tried setTimeout but don't know how to implement it or whether its even the right way to do it.

renderSelected() {
var temp=[];
for (var i = 0; i < 11; i++) {
temp.push(this.selected(i))
}
return temp;
}

selected(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}


Update based on the answer but it still doesn't work. The code in the answer was too different since this is React Native.

renderLater(i) {
TimerMixin.setTimeout(() => {
this.selected(i);
}, 100);
}

renderSelected() {
var temp=[];
for (var i = 0; i < 11; i++) {
temp.push(this.renderLater(i))
}
return temp;
}

selected(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}

Answer

Based on the code, the problem is that you return temp which actually contains nothing, since renderLater returns nothing.

A solution is to create an element with a state, and depending on the state your render one or more elements. This is similar to the timer element on the reactjs page, where the state is updated every second triggering a new rendering. Instead of changing the ticker every second, you can increase a counter and in renderSelected() display all the elements up to that counter. There is no renderLater, just a this.setState() called regularly triggering a new rendering with a different number of elements.

var MyClass = React.createClass({
  getInitialState: function() {
    return {counter: 0};
  },
  tick: function() {
    if (this.state.counter >= 10) return;
    this.setState({counter: this.state.counter + 1});
  },
  componentDidMount: function() {
    this.interval = setInterval(() => {this.tick();}, 50);
  },
  componentWillUnmount: function() {
    clearInterval(this.interval);
  },
  render: function() {
      var temp=[];
      for (var i = 0; i <= 10 && i <= this.state.counter; i++) {
          temp.push(this.selected(i))
      }
      return temp;
  },
  selected: function(number) {
      return (<View key={number} style={styles.normal}  >
         <Text>{number}</Text>
      </View>);
  }
});

ReactDOM.render(<MyClass />, mountNode);

Live demo

You can also instead create all the elements separately from the start with each an empty render() function in the beginning and have them display something when enough time is elapsed. For that, you can still use the x.setState() function for each separate element to trigger a new rendering. This avoids erasing and redrawing already drawn elements at each tick.


Old answer:

You can do a global queue for delayed stuff.

var queue = [];

/* executes all element that have their delay elapsed */
function readQueue() {
    var now  = (new Date()).getTime();
    for (var i = 0; i < queue.length; i++) {
        if (queue[i][0] <= now) { 
            queue[i][1]();
        } else {
            if(i != 0) queue = queue.slice(i);
            return;
        }
    }
    queue = [];
}

/* Delay is in milliseconds, callback is the function to render the element */
function addQueue(delay, callback) {
    var absoluteTime = (new Date()).getTime() + delay;
    for (var i = 0; i < queue.length; i++) {
        if (absoluteTime < queue[i][0]) {
             queue.splice(i, 0, [absoluteTime, callback]);
             return;
        }
    }
    queue.push_back([absoluteTime, callback]);
}

var queueTimer = setInterval(readQueue, 10); //0.01s granularity

With that queue, if you want to render something some elements every 50ms later, then you can just do:

function renderElementLater(time, index) {
    /* render */
    addQueue(time, function(){ReactDOM.render(selected(index), mountNode);}):
}
for (var i = 0; i <= 10; i++) {
    renderElementLater(50*i, i);
}

You can change the granularity (when the queue is read and checked) to something even less than every 10ms for finer control.

Although, I don't see what's the problem with the setTimeout. You could just do:

function renderElementLater(time, index) {
    /* render */
    setTimeout(function(){ReactDOM.render(selected(index), mountNode);}, time):
}
for (var i = 0; i <= 10; i++) {
    renderElementLater(50*i, i);
}

Maybe your problem came that if you want to use the value of a variable that changes in a callback created in a loop, then it needs to be enclosed by another function (like I did by creating the new function renderElementLater instead of directly putting function code there).

Comments