ReactJS: Uncaught RangeError after setState - javascript

I'm trying to create a simple drawing application using ReactJS and Flux. However, I came across a bug [Uncaught RangeError: Maximum call stack size exceeded] (it's more specifically a ReactJS problem) I've been trying figure out for a while now. It occurs when I'm trying to do setState() in a component. Below is part of my code to better illustrate my problem.
DrawingCanvas.react.js
var React = require('react');
var MouseInteractionsMixin = require('../utils/MouseInteractionsMixin');
var StrokeActionCreators = require('../actions/StrokeActionCreators');
var StrokeStore = require('../stores/StrokeStore');
function getStateFromStores() {
return {
currentStroke: StrokeStore.getCurrentStroke()
};
}
var DrawingCanvas = React.createClass({
mixins: [MouseInteractionsMixin],
styles: {
canvas: {
border: '1px solid black'
}
},
getInitialState: function() {
return getStateFromStores();
},
componentWillMount: function() {
StrokeStore.addChangeListener(this._update);
},
componentDidMount: function() {
this._onChange();
},
componentDidUpdate: function() {
this._onChange();
},
_onChange: function() {
if(this.state.dragging) {
StrokeActionCreators.beginStroke(this.state.mouse.point);
} else {
if(this.state.mouse.event == 'mouseup') {
StrokeActionCreators.endStroke(this.state.currentStroke);
}
}
},
_update: function() {
this.setState(getStateFromStores()); // where the problem occurs
context = this.getDOMNode().getContext('2d');
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
this._draw(context);
},
_draw: function(context) {
context.strokeStyle = '#000000';
context.lineJoin = 'round';
context.lineWidth = 5;
// Draw current stroke (incompleted)
for(var index = 1; index < this.state.currentStroke.points.length; index++) {
context.beginPath();
context.moveTo(this.state.currentStroke.points[index - 1].x, this.state.currentStroke.points[index - 1].y);
context.lineTo(this.state.currentStroke.points[index].x, this.state.currentStroke.points[index].y);
context.closePath();
context.stroke();
}
/*// Draw the other completed strokes
for(var strokeIndex = 0; strokeIndex < this.state.strokes.length; strokeIndex++) {
var stroke = this.state.strokes[strokeIndex];
for(var currentPointIndex = 1; currentPointIndex < stroke.points.length; currentPointIndex++) {
var previousPointIndex = currentPointIndex - 1;
context.beginPath();
context.moveTo(stroke.points[previousPointIndex].x, stroke.points[previousPointIndex].y);
context.lineTo(stroke.points[currentPointIndex].x, stroke.points[currentPointIndex].y);
context.closePath();
context.stroke();
}
}*/
},
render: function() {
return (
<canvas style={this.styles.canvas} width={this.props.width} height={this.props.height} />
);
}
});
module.exports = DrawingCanvas;
Basically, what I'm trying to do is trigger an action when the mouse is clicked and starts moving on the canvas, which sends the mouse position data to the StrokeStore. The StrokeStore stores all the mouse position data, so that eventually the DrawingCanvas component requests for the data (hence the getStateFromStores() function) and draws on the canvas, but when I try to set the state in the _update() function and try drawing on the canvas, there would be a large amount of mouse position data coming in (even after moving the mouse a tiny bit then stopping it) and an error "Uncaught RangeError: Maximum call stack size exceeded" would come up.
I've tried not storing the mouse position data as a state of the component, but rather separate variables outside the component which I use inside the component, and it works perfectly fine, so I think it's a problem with the setState().
I'd really like to get it working as states (or any other way so that I can contain the data in the stores).
EDIT:
StrokeStore.js
var AppDispatcher = require('../dispatchers/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var ActionTypes = AppConstants.ActionTypes;
var CHANGE_EVENT = 'change';
var _currentStroke = {
// Points array contains point objects with x and y variables
points: []
};
// Strokes array contains stroke objects with similar structure to currentStroke
var _strokes = [];
function _addPointToCurrentStroke(point) {
_currentStroke.points.push(point);
}
function _addStrokeToStrokes(stroke) {
_strokes.push(stroke);
}
function _clearCurrentStroke() {
_currentStroke = {
points: []
};
}
var StrokeStore = assign({}, EventEmitter.prototype, {
emitChange: function() {
this.emit(CHANGE_EVENT);
},
/**
* #param {function} callback
*/
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getCurrentStroke: function() {
return _currentStroke;
},
getStrokes: function() {
return _strokes;
}
});
AppDispatcher.register(function(payload) {
var action = payload.action;
switch(action.type) {
case ActionTypes.BEGIN_STROKE:
_addPointToCurrentStroke(action.point);
break;
case ActionTypes.END_STROKE:
_addStrokeToStrokes(action.stroke);
_clearCurrentStroke();
break;
default:
}
StrokeStore.emitChange();
});
module.exports = StrokeStore;
Any help is appreciated, thanks!

Related

How to invoke a function when a class is instantiated

I have two classes, one of which is a subclass of the other. What I want to do is have one of the functions, step, invoked when the subclass is instantiated. This will cause the subclass to blink. I can see how to do it with functional instantiation but not with pseudoclassical. I've attached the jquery and javascript. Basically, when the button is clicked, I want the blinky dancer to blink, this should happen via the step function. Maybe I'm calling it incorrectly in the dancer superclass?
$(document).ready(function() {
window.dancers = [];
$('.addDancerButton').on('click', function(event) {
var dancerMakerFunctionName = $(this).data('dancer-maker-function-name');
console.log(dancerMakerFunctionName);
var dancerMakerFunction = window[dancerMakerFunctionName];
var dancer = new dancerMakerFunction(
$("body").height() * Math.random(),
$("body").width() * Math.random(),
Math.random() * 1000
);
$('body').append(dancer.$node);
});
});
var makeBlinkyDancer = function(top, left, timeBetweenSteps) {
makeDancer.call(this, top, left, timeBetweenSteps);
};
makeBlinkyDancer.prototype = Object.create(makeDancer.prototype);
makeBlinkyDancer.prototype.constructor = makeBlinkyDancer;
var oldStep = makeBlinkyDancer.prototype.step;
makeBlinkyDancer.prototype.step = function() {
oldStep();
this.$node.toggle();
};
var makeDancer = function(top, left, timeBetweenSteps) {
this.$node = $('<span class="dancer"></span>');
};
makeDancer.prototype.step = function() {
setTimeout(step.bind(this), timeBetweenSteps);
};
makeDancer.prototype.setPosition = function (top, left) {
var styleSettings = {
top: top,
left: left
};
this.$node.css(styleSettings);
};
// makeDancer.prototype.setPosition();
You can call the method when the subclass is instantiated by calling init in the constructor. In the example and demo I called this method makeBlink rather than step or oldStep. Hope that makes it clear.
// the subclass BlinkyDancer
var BlinkyDancer = function (top, left, height, width, timeBetweenSteps) {
// ... get props from parent class
// make blink when sublcass instantiated
this.init = this.makeBlink();
};
Full Demo/Solution
Demo: https://codesandbox.io/s/call-method-when-class-instantiated-mnesxt
HTML
<body>
<div id="app">
<button id="make-blink">Make Blink</button>
<button id="stop-blink">Stop Blink</button>
</div>
</body>
JS
import $ from "jquery";
var Dancer = function (top, left, height, width) {
this.$node = $('<div class="dancer"></div>');
this.top = top;
this.left = left;
this.height = height;
this.width = width;
this.bg = "blue";
};
Dancer.prototype.putDancerOnScreen = function () {
this.$node.height(this.height).width(this.width);
this.$node.css("background-color", "blue");
this.$node.css("position", "absolute");
$("body").append(this.$node);
};
Dancer.prototype.setPosition = function () {
this.$node.css("top", this.top);
this.$node.css("left", this.left);
};
var myDancer = new Dancer(
Math.floor(Math.random() * 500),
Math.floor(Math.random() * 500),
50,
50
);
myDancer.putDancerOnScreen();
myDancer.setPosition();
// the subclass BlinkyDancer
var BlinkyDancer = function (top, left, height, width, timeBetweenSteps) {
Dancer.call(this, top, left, height, width); // get the props set up by Dancer
this.timeBetweenSteps = timeBetweenSteps;
this.blinkerId = null;
// make blink when sublcass instantiated
this.init = this.makeBlink();
};
// set inheritance and Dancer constructor function
BlinkyDancer.prototype = Object.create(Dancer.prototype);
BlinkyDancer.prototype.constructor = Dancer;
// add subclass methods
BlinkyDancer.prototype.makeBlink = function () {
if (this.blinkerId !== null) {
return;
}
let count = 0;
let blinkerId = setInterval(() => {
// do whatever thing you want to indicate blinking/dancing
if (count % 2 === 0) {
this.$node.css("background-color", "red");
} else {
this.$node.css("background-color", "blue");
}
count++;
}, this.timeBetweenSteps);
this.blinkerId = blinkerId;
console.log("blinkder id set: ", this.blinkerId);
};
BlinkyDancer.prototype.stopBlink = function () {
if (this.blinkerId === null) {
// already blinking
return;
}
if (this.blinkerId !== null) {
clearInterval(this.blinkerId);
console.log("blink id cleared: ", this.blinkerId);
this.blinkerId = null;
}
};
// instantiate a new subclass
let myBlinkyDancer = new BlinkyDancer(
Math.floor(Math.random() * 500),
Math.floor(Math.random() * 500),
50,
50,
25
);
// use parent class methods to put the element on screen
myBlinkyDancer.putDancerOnScreen();
myBlinkyDancer.setPosition();
const makeBlinkButton = document.getElementById("make-blink");
const stopBlinkButton = document.getElementById("stop-blink");
makeBlinkButton.addEventListener("click", function () {
myBlinkyDancer.makeBlink();
});
stopBlinkButton.addEventListener("click", function () {
myBlinkyDancer.stopBlink();
});
Explanation
The Dancer class takes some props related to positioning and dimensions. It gets a blue background and has two methods for adding the element to the DOM and setting the position on screen.
BlinkyDancer is a subclass that inherits all the props of Dancer as well as two new props and two new methods.
What I want to do is have one of the functions, step, invoked when the
subclass is instantiated.
When a new BlinkyDancer is instantiated, we call makeBlink right away with init so the element starts blinking.
// make blink when sublcass instantiated
this.init = this.makeBlink();
I used an alternating background color to demonstrate the blinking effect.
The makeBlink and stopBlink methods work in tandem with the new props timeBetweenSteps and blinkerId.
when the button is clicked, I want the blinky dancer to blink, this
should happen via the step function.
Buttons are wired up to trigger starting and stopping the blinking effect. I've replaced step in your example with simple and declarative methods for making the blink and stopping it.
makeBlinkButton.addEventListener("click", function () {
myBlinkyDancer.makeBlink();
});
The blinking effect uses setInterval to toggle the background color using the timeBetweenSteps prop as the interval duration. startBlink starts the interval and sets blinkerId to the corresponding interval ID. To stop the blinking effect, clearInterval is passed the blinkerId to clear it (stopping the blinking effect) before resetting it to null.
BlinkyDancer.prototype.makeBlink = function () {
if (this.blinkerId !== null) {
return;
}
let count = 0;
let blinkerId = setInterval(() => {
// do whatever thing you want to indicate blinking/dancing
if (count % 2 === 0) {
this.$node.css("background-color", "red");
} else {
this.$node.css("background-color", "blue");
}
count++;
}, this.timeBetweenSteps);
this.blinkerId = blinkerId;
console.log("blinkder id set: ", this.blinkerId);
};
BlinkyDancer.prototype.stopBlink = function () {
if (this.blinkerId === null) {
// already blinking
return;
}
if (this.blinkerId !== null) {
clearInterval(this.blinkerId);
console.log("blink id cleared", this.blinkerId);
this.blinkerId = null;
}
};
Maybe I'm calling [step] incorrectly in the dancer superclass?
It looks like you were on the right track but missing parens to call the method. In your example code, rather than:
makeBlinkyDancer.prototype.step;
You would do:
makeBlinkyDancer.prototype.step();

How to scale TileMap in Phaser?

My question is specific about Phaser game engine
I'm completely new to the Phaser so maybe I'm missing something simple here. I'm trying to write a simple mobile game and I'm having troubles finding tutorials/sources/examples about mobile aspects of game dev using Phaser.
In my game I have a few stages and a game is defined like:
var game = new Phaser.Game(gameWidth, gameHeight, Phaser.AUTO, 'main');
game.state.add('Boot', myGame.Boot);
game.state.add('Preloader', myGame.Preloader);
game.state.add('Stage11', myGame.Stage11);
in the 'Boot' I'm setting up scale
var myGame = {};
myGame.Boot = function (game) {
};
myGame.Boot.prototype = {
init: function () {
this.scale.scaleMode = Phaser.ScaleManager.USER_SCALE;
this.scale.setUserScale(myGame.Metrics.scaleX, myGame.Metrics.scaleY);
this.scale.pageAlignHorizontally = true;
this.scale.pageAlignVertically = true;
},
create: function () {
this.state.start('Preloader');
}
};
myGame.Metrics.scaleX and myGame.Metrics.scaleY determined based on some logic I found online. Basically we assume that game is 800 x 500px and depending on the current dimensions we are finding our scaleX and ScaleY
Preloader is big, here is a tile portion:
myGame.Preloader = function (game) {
var fx;
};
myGame.Preloader.prototype = {
preload: function () {
this.load.tilemap('Stage1-1', 'assets/MY/Stage1-1.json', null, Phaser.Tilemap.TILED_JSON);
this.load.image('Tiles', 'assets/MY/tiles.png');
this.load.image('car', 'assets/sprites/car90.png');
},
start: function () {
this.state.start('Stage11');
}
};
And the last piece is Stage11:
myGame.Stage11 = function () {
};
var sprite;
var cursors;
myGame.Stage11.prototype = {
create: function () {
this.physics.startSystem(Phaser.Physics.ARCADE);
this.map = this.add.tilemap('Stage1-1');
//the first parameter is the tileset name as specified in Tiled, the second is the key to the asset
this.map.addTilesetImage('Tiles', 'Tiles');
//create layer
this.blockedLayer = this.map.createLayer('blockedLayer');
//collision on blockedLayer
this.map.setCollisionBetween(1, 2000, true, 'blockedLayer');
this.blockedLayer.resizeWorld();
sprite = this.add.sprite(50, 50, 'car');
sprite.anchor.setTo(0.5, 0.5);
this.physics.enable(sprite);
this.camera.follow(sprite);
cursors = this.input.keyboard.createCursorKeys();
},
update: function () {
sprite.body.velocity.x = 0;
sprite.body.velocity.y = 0;
sprite.body.angularVelocity = 0;
if (cursors.left.isDown) {
sprite.body.angularVelocity = -200;
}
else if (cursors.right.isDown) {
sprite.body.angularVelocity = 200;
}
if (cursors.up.isDown) {
sprite.body.velocity.copyFrom(this.physics.arcade.velocityFromAngle(sprite.angle, 300));
}
},
render: function () {
// Camera
this.game.debug.cameraInfo(this.game.camera, 32, 32);
}
};
My TileMap was created using "Tiled" and it is 20x200 tiles (each tile is 70x70px) so it is 1400px by 14000px here it is:
And that's how it looks like on Simulator:
I'm a little bit desperate at this point. Here are a few questions:
How to make TileMap to fit the screen (let's assume screen size is
going to vary)?
Do I need to resize the TileMap?
What's wrong here?
Any help appreciated. Thanks!

Collision detection via overlap() not working in phaser.io

Please note that I've created the following code entirely using Phaser.io.
So I've created an enemy group and also a weapon group (using the inbuilt game.add.weapon()).
I check for overlap between these two groups but for some reason the hitEnemy() function is never fired.
I believe this has something to do with using the inbuilt weapon group, but I can't figure out what exactly it is.
var playState = {
preload: function () {
game.load.image('player', 'assets/player.png');
game.load.image('enemy', 'assets/enemy.png');
game.load.image('floor', 'assets/floor.png');
game.load.image('bullet', 'assets/bullet.png');
},
create: function () {
game.stage.backgroundColor = '#000000';
game.physics.startSystem(Phaser.Physics.ARCADE);
game.renderer.renderSession.roundPixels = true;
this.cursors = game.input.keyboard.createCursorKeys();
this.fireButton = game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
this.createBullets();
this.createEnemies(game.width/2, 120, 'enemy');
},
update: function () {
game.physics.arcade.collide(this.player, this.floor);
game.physics.arcade.overlap(this.weapon, this.enemies, this.hitEnemy, null, this);
},
createBullets: function () {
this.weapon = game.add.weapon(30, 'bullet');
this.weapon.bulletKillType = Phaser.Weapon.KILL_WORLD_BOUNDS;
this.weapon.bulletSpeed = 850;
this.weapon.fireRate = 100;
this.weapon.trackSprite(this.player);
},
createEnemies: function (x,y ,size) {
this.enemies = game.add.group();
this.enemies.enableBody = true;
this.enemies.physicsBodyType = Phaser.Physics.ARCADE;
var asteroid = this.enemies.create(x, y, size);
asteroid.anchor.setTo(0.5,0.5);
asteroid.name = "enemy1";
asteroid.body.immovable;
},
hitEnemy: function (player, enemy) {
this.enemy.kill();
console.log("Hit");
},
};
Found the solution.
Instead of checking overlap with this.enemies and this.weapon. I was supposed to check collision with this.enemies and this.weapon.bullets

Control multiple canvas animations

Is it possible to hook into a Canvas animation that has already been drawn?
I'm trying to create 3 seperate tracks that must be controlled by a "Start" and "Stop" button, but when pressing the Start button of the first canvas , it makes the last canvas run.
See current codepen progress
This is what I have so far.
class Animation{
constructor(){
this.options = {
left : 0,
animate : false
}
let tracks = $('.line');
let self = this;
tracks.each(function(){
self.startbtn = $(this).find('button.start');
self.stopbtn = $(this).find('button.stop');
self.canvas = $(this).find('canvas').get(0);
self.canvas.width = 1000;
self.canvas.height = 30;
self.ctx = self.canvas.getContext('2d');
self.draw(self.canvas,self.ctx);
self.startbtn.bind('click',() => {
self.options.animate = true;
self.draw(self.canvas,self.ctx);
})
});
}
draw(c,ctx){
ctx.clearRect(0,0,c.height,c.width);
ctx.fillRect(this.options.left,0,1,30);
if(this.options.animate){
requestAnimationFrame(() => {
console.log('done');
this.options.left += 1;
console.log(this.options.left)
this.draw(c,ctx);
});
}
}
}
new Animation();
Your code has scope issues and you had to insure that each track is operating separate from its constructor.
This is where i'd made sure information is entirely independent for the given track/parent element.
this.options = *set*;//left and animate separate for own process
this.node = *set*;//nodes for each track
this.ctx = *set*;//draw command context set at the parent
this.draw = *set*;//draw function moved to track
Here is complete edit to your code:
class Animation{
constructor(){
var tracks = $('.line');
var self = this;
tracks.each(function () {
this.options = {//options seperate to parent track node
left: 0,
animate: false
};
this.node = {//store child nodes for access
startbtn: $(this).find('button.start'),
stopbtn: $(this).find('button.stop'),
canvas: $(this).find('canvas').get(0)
};
this.node.canvas.width = 1000;
this.node.canvas.height = 30;
this.ctx = this.node.canvas.getContext('2d');
this.node.startbtn.bind('click',() => {// () => parentNode instantiation
this.options.animate = true;
this.draw(this.node.canvas, this.ctx);
});
this.draw = self.draw;
});
}
draw(c,ctx){
parent = c.parentNode;
ctx.clearRect(0,0,c.height,c.width);
ctx.fillRect(parent.options.left,0,1,30);
if(parent.options.animate){
requestAnimationFrame(() => {
console.log('done');
parent.options.left += 1;
console.log(parent.options.left)
parent.draw(c,ctx);
});
}
}
}
new Animation();
Scope and object handling needed to be done.
http://codepen.io/anon/pen/JKzBLK

Troubleshooting an infinite loop error

I am trying to draw an array of images on canvas at random x,y but it gives me an infinite loop.... here is my code
var fruits = ["fruit1.png", "fruit2.png", "fruit3.png", "fruit4.png"];
var monsterReady1 = true;
var draw = function() {
for (var i = 0; i < fruits.length; i++) {
monsterImage1 = new Image();
monsterImage1.onload = function () {
monster1.x = (Math.random() * (canvas.width - 100));
monster1.y = (Math.random() * (canvas.height - 100));
ctx.drawImage(this, monster1.x, monster1.y);
};
monsterImage1.src = fruits[i];
}
};
var render = function() {
if (monsterReady1) {
draw();
}
var main = function () {
update();
render();
requestAnimationFrame(main);
};
You have recursive in main() function. This is normal behaviour of requestAnimationFrame(). It is normal to call that function infinite to draw canvas each frame.
There is also recursion when render() executes. You don't need to call render again and again. Pass out render call from render() body
var render = function(){
if (monsterReady1) {
draw();
}
var main = function () {
update();
render(); // THIS is error. You should not call render again
requestAnimationFrame(main); // This will call main function infinite loop. Expected.
}
};
//render(); // Better to call it here
By the way in code you provide there is a syntax error. You missed one closing bracket

Categories

Resources