diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index f1f6b9a5a..5eb317b9b 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -17,6 +17,8 @@ BlazeComponent.extendComponent({ this.isConverting = new ReactiveVar(false); this.isMigrating = new ReactiveVar(false); this._swimlaneCreated = new Set(); // Track boards where we've created swimlanes + this._boardProcessed = false; // Track if board has been processed + this._lastProcessedBoardId = null; // Track last processed board ID // The pattern we use to manually handle data loading is described here: // https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager @@ -28,21 +30,33 @@ BlazeComponent.extendComponent({ const handle = subManager.subscribe('board', currentBoardId, false); - Tracker.nonreactive(() => { - Tracker.autorun(() => { - if (handle.ready()) { + // Use a separate autorun for subscription ready state to avoid reactive loops + this.subscriptionReadyAutorun = Tracker.autorun(() => { + if (handle.ready()) { + // Only run conversion/migration logic once per board + if (!this._boardProcessed || this._lastProcessedBoardId !== currentBoardId) { + this._boardProcessed = true; + this._lastProcessedBoardId = currentBoardId; + // Ensure default swimlane exists (only once per board) this.ensureDefaultSwimlane(currentBoardId); // Check if board needs conversion this.checkAndConvertBoard(currentBoardId); - } else { - this.isBoardReady.set(false); } - }); + } else { + this.isBoardReady.set(false); + } }); }); }, + onDestroyed() { + // Clean up the subscription ready autorun to prevent memory leaks + if (this.subscriptionReadyAutorun) { + this.subscriptionReadyAutorun.stop(); + } + }, + ensureDefaultSwimlane(boardId) { // Only create swimlane once per board if (this._swimlaneCreated.has(boardId)) { @@ -441,39 +455,50 @@ BlazeComponent.extendComponent({ this._isDragging = false; // Used to set the overlay this.mouseHasEnterCardDetails = false; + this._sortFieldsFixed = new Set(); // Track which boards have had sort fields fixed // fix swimlanes sort field if there are null values const currentBoardData = Utils.getCurrentBoard(); if (currentBoardData && Swimlanes) { - const nullSortSwimlanes = currentBoardData.nullSortSwimlanes(); - if (nullSortSwimlanes.length > 0) { - const swimlanes = currentBoardData.swimlanes(); - let count = 0; - swimlanes.forEach(s => { - Swimlanes.update(s._id, { - $set: { - sort: count, - }, + const boardId = currentBoardData._id; + // Only fix sort fields once per board to prevent reactive loops + if (!this._sortFieldsFixed.has(`swimlanes-${boardId}`)) { + const nullSortSwimlanes = currentBoardData.nullSortSwimlanes(); + if (nullSortSwimlanes.length > 0) { + const swimlanes = currentBoardData.swimlanes(); + let count = 0; + swimlanes.forEach(s => { + Swimlanes.update(s._id, { + $set: { + sort: count, + }, + }); + count += 1; }); - count += 1; - }); + } + this._sortFieldsFixed.add(`swimlanes-${boardId}`); } } // fix lists sort field if there are null values if (currentBoardData && Lists) { - const nullSortLists = currentBoardData.nullSortLists(); - if (nullSortLists.length > 0) { - const lists = currentBoardData.lists(); - let count = 0; - lists.forEach(l => { - Lists.update(l._id, { - $set: { - sort: count, - }, + const boardId = currentBoardData._id; + // Only fix sort fields once per board to prevent reactive loops + if (!this._sortFieldsFixed.has(`lists-${boardId}`)) { + const nullSortLists = currentBoardData.nullSortLists(); + if (nullSortLists.length > 0) { + const lists = currentBoardData.lists(); + let count = 0; + lists.forEach(l => { + Lists.update(l._id, { + $set: { + sort: count, + }, + }); + count += 1; }); - count += 1; - }); + } + this._sortFieldsFixed.add(`lists-${boardId}`); } } },