Uncaught RangeError: Maximum call stack size exceeded


#1

I have an e-commerce site and want clicking of an item to reflect on both the item and a cart pegged at the top of the page. This works fine. I also want to maintain state across pages, such that if the server returns an already loaded cart, all corresponding items from that cart on the current page get selected. Attempts to ‘re-select’ these items throws the error Uncaught RangeError : Maximum call stack size exceeded

I understand the interpreter is complaining about the recursive process of trying to unselect cart items, reselect them in the items list ad infinitum. I’m trying to work around this by first removing all the cart items like so:

selectClones = function () {

		var retain = [];
	 	
	 	if (location.search.match(/cid=(\d+)/)) $('aside li').each((i,e) => {

	 		var e = $(e), team = e.find('span:first').text(), sel = e.find('span:last').text(),

	 		selInd = $(`th:contains(${sel})`).index();

	 		retain.push({t: team, s: selInd});
	 	}).remove();

	 	for (var i = retain.length - 1; i >= 0; i--) {
	 		
	 		$(`td:contains(${retain[i].t})`).parent().children().eq(retain[i].s).trigger('click');
	 	}
	}

Surprisingly, it doesn’t work unless I run this same snippet in the developer console. What else can I try?

the click listener

$('main').on('click', 'td.pointer, aside li.pointer', function(e) {

		if ($(e.target).is('td') && !$(e.target).hasClass('selected')) addSelection().call(this, null);

		else removeSelection().call(this, null);
	});

addSelection is a huge function that basically checks if a row of this item is preselected and tries to deselect it (this condition is what throws the error), then logs a custom object with details pertaining to this selected item for synchronization between the table list and the aside cart. I think this is more straightforward than posting the actual code but if that’ll make things clearer, let me know so I can post it too.Many thanks.


#2

Why is this process recursive, instead of iterative?

In any case, it sounds like your recursion has run away into an infinite loop, so you’d need to show us the… actually… recursive part. Cause what you’ve shown us thus far isnt actually recursive.


#3

I probably need an idempotent flag that determines if selSib shouldn’t be clicked if the GET search parameters are present.

var addSelection = function() {

		return function() {

			var selSib = $(this).siblings().filter((i,e) => $(e).is('.selected')), // throws an error here

			complFx = $(this).siblings().parent().children().eq(2).text() == '-:-';

			if (!selSib.length && complFx) {

				var sel = $('th').eq($(this).index()).text(),

				date = $(this).siblings().first().text(),

				teams = $(this).siblings().eq(1).text(),

				league = $(this).parent().prevAll().filter((i,e) => $(e).find('.countryRow').length).first().text(),

				vars = {teams: teams, league: league, selectionObj: {selection: sel, status: -1}, date: date},

				templ = $('#new-sel').prop('content').firstElementChild,

				selUi = $(templ).clone().children().each(function(i,e) {

					if (i == 0) $(e).attr({'title': league}).text(teams);

					if (i == 1) $(e).text(sel);
				}).parent();


				$(this).addClass('selected');

				userSels.push(vars);

				$('.card ul').append(selUi).parent().css('height', 'fit-content').each(setCartHeight() );
			}

			else {
				selSib.trigger('click'); // BUT THIS IS WHAT I BELIEVE CAUSES THE RECURSION

				$(this).trigger('click');
			}
		}
},

	removeSelection = function() {

		return function() {

			var name = $(this).is('li') ? $(this).find('span:first').text() : $(this).siblings().eq(1).text(),

			ind =  userSels.findIndex(obj => obj.teams == name),

			selInd = $(`th:contains(${userSels[ind].selectionObj.selection})`).index();

			userSels = userSels.filter((e,i) => i != ind);


			$('aside li.pointer').eq(ind).remove();

			$(`td:contains(${name})`).parent().children().eq(selInd).removeClass('selected');
		}
	};

#4

I was correct. The logic I surmised above is the solution. By adding an additional var cloneNonce and implementing it like

var cloneNonce = 0;

// modify addSelection() else block to this
			else {
				selSib.trigger('click');

				if (!cloneNonce) $(this).trigger('click');
			}
// and update the selectClones() function as follows


	 	cloneNonce = 1;

	 	for (var i = retain.length - 1; i >= 0; i--) {
	 		
	 		$(`td:contains(${retain[i].t})`).parent().children().eq(retain[i].s).trigger('click');
	 	}
	 	
	 	cloneNonce = 0;

I’m able to retain the behaviour as well as make the functionality dynamic enough to be used when needed.