Wait for data on blaze when using a ReactiveVar

I’m doing some intense calculation on the client side, which doesn’t involve a subscription or server data, and I need Blaze to wait for a ReactiveVar. Everything I’ve read so far involves subscriptions. Is there a way to do that?

This is what I have

var shuffle = require('shuffle-array'); // from npm

const colorSet = ['red','gold','blue','green','orange','turquoise','wheat','fuchsia','purple'];
const colorNames = ['VERMELHO','AMARELO','AZUL','VERDE','LARANJA','TURQUESA','BEGE','ROSA','ROXO'];

var limit = colorSet.length;

// helper functions
function getRandomPosition() {
  return Math.floor(Math.random() * (limit + 1));
}

function getRightColor() {
  let position = getRandomPosition();
  let rightColor = {
    color: colorSet[position],
    name: colorNames[position],
    right: 'right-option',
  };
  return rightColor;
}

function getWrongColor() {
  let position1 = getRandomPosition();
  let position2 = getRandomPosition();
  while (position1 === position2) {
    position2 = getRandomPosition();
  }
  let wrongColor = {
    color: colorSet[position1],
    name: colorNames[position2]
  };
  return wrongColor;
}

function make4Circles() {
  let circles = [];
  circles.push(getRightColor());

  for (let i = 0; i < 3; i++) {
    circles.push(getWrongColor());
  }
  return shuffle(circles);
}


////
// TEMPLATE HELPERS
///
Template.gameArea.onCreated(function () {
  this.circleArray = new ReactiveVar(make4Circles());
});

Template.gameArea.helpers({
  circles: () => {
    return Template.instance().circleArray.get();
  }
});

The thing is that Blaze is rendering the page before the circleArray is ready, with all the data.

circleArray is the result of a shuffled array composed by 4 objects created by random combination os colorSet and colorNames that needs to have 1 right and 3 wrong combinations.

This should do the trick:

{{#if circles}}
  //Display your stuff
{{else}}
  <h1>Loading...</h1>
{{/if}}

However, the way you’re doing the calculation, doesn’t it freeze up the client while the calculation is running?

If the reactiveVar is not really monitoring a reactive data source, you can use it more as a toggle switch with a lightweight boolean value, and store the results from the function on the template itself or within scope. So the function will just store the data to a this.result array for example, and then the return statement can set the reactivevar to true.
That way you are just controlling if the template is displaying or not, maybe showing a processing or loading template instead. Then the template will render once with the completed array, not as soon as the array becomes available. Remember that an empty array is not falsy, like an empty object, but the length if 0, is falsy. #if circles.length

Unfortunately it doesn’t work… :frowning:

He only stores an array in circleArray() once the calculation is fully complete though, until then it’s undefined. There’s never an empty array, so my code should work properly.

If your calculation is taking several seconds though (and you cannot make it faster), I would suggest making it asynchronous. Replace the main loop with a recursive Meteor.defer(), and instead of returning a value, have it call circleArray.set() on completion. Then it won’t lock up the browser while it’s running.

I used this code exactly and the data was rendered on the template.

I only removed the shuffle() call since you didn’t include it

and used in the template like

<ul>
  {{#each circle in circles}}
    <li>{{circle.color}}</li>
  {{/each}}
</ul>


const colorSet = ['red','gold','blue','green','orange','turquoise','wheat','fuchsia','purple'];
const colorNames = ['VERMELHO','AMARELO','AZUL','VERDE','LARANJA','TURQUESA','BEGE','ROSA','ROXO'];

var limit = colorSet.length;

// helper functions
function getRandomPosition() {
  return Math.floor(Math.random() * (limit + 1));
}

function getRightColor() {
  let position = getRandomPosition();
  let rightColor = {
    color: colorSet[position],
    name: colorNames[position],
    right: 'right-option',
  };
  return rightColor;
}

function getWrongColor() {
  let position1 = getRandomPosition();
  let position2 = getRandomPosition();
  while (position1 === position2) {
    position2 = getRandomPosition();
  }
  let wrongColor = {
    color: colorSet[position1],
    name: colorNames[position2]
  };
  return wrongColor;
}

function make4Circles() {
  let circles = [];
  circles.push(getRightColor());

  for (let i = 0; i < 3; i++) {
    circles.push(getWrongColor());
  }
  console.log('circles:', circles)
  return circles
}





////
// TEMPLATE HELPERS
///
Template.gameArea.onCreated(function () {
  this.circleArray = new ReactiveVar(make4Circles());
})


Template.gameArea.helpers({
  circles () => {
    return Template.instance().circleArray.get();
  }
})


I haven’t solved this problem yet… The shuffle function is important in this case and altought I haven’t copied that, it’s a package I downloaded from NPM.

The code works, but if you reload the page a couple times, you will see the template is sometimes rendered without some data and I understand that it happens because the rendering happens before the data is done.

I definitely need more directions to solve it.

You’re saying you have a race condition? Where the template sometimes renders before the data is ready, and remains empty?

That should never happen with Blaze when you’re using ReactiveVars, because it’s reactive. So even if the template renders before the data is ready, as soon as the data becomes available, the template will rerender.

The ReactiveVar always have the 4 objects, but the objects seem to not have all the right atributes. Since blaze is reactive, I believed this wouldn’t happen but it is. You can take a look at the project if you want: https://github.com/kemelzaidan/truecolor-clone

You will see the circles is sometimes black (and there is no black on the colorSet) and sometimes it doesn’t have the name on it.