I’m working on a Vue.js app that syncs categories with a server via an API. I’m having issues with category display after setting parent and child categories.
Situations encountered:
- No category assignments: Works fine on both client and API.
- Parent category assigned: Correct nesting on the client, API data accurate.
- Parent + child categories assigned: Displays correctly on both client and API.
- After “Cancel Child”: Before reload, categories are correct, but after reload, they desync, and the structure differs from the API.
The fetchCategorie
s function retrieves categories from the server, but syncing fails after updating the structure.
How can I ensure proper synchronization between the client and API to prevent discrepancies in the category hierarchy?
async fetchCategories() {
try {
const response = await fetch(`${this.apiUrl}/categories`);
this.logResponse(response, 'fetchCategories');
if (!response.ok) {
throw new Error('Network response was not ok');
}
this.categories = await response.json();
this.checkCyclicDependencies(); // Check for cyclic dependencies after fetching
console.log("Fetched categories:", this.categories);
} catch (error) {
console.error("Error fetching categories:", error);
this.showMessage(`Failed to load categories: ${error.message}`, 'error');
}
}
// Method for checking cyclic dependencies
checkCyclicDependencies() {
const visited = new Set();
const recStack = new Set();
const hasCycle = (id) => {
if (!visited.has(id)) {
visited.add(id);
recStack.add(id);
const category = this.categories.find(cat => cat.ID === id);
if (category) {
if (recStack.has(category.ParentID)) {
return { hasCycle: true, cycleStart: category.ParentID };
}
if (category.ParentID !== null) {
const result = hasCycle(category.ParentID);
if (result.hasCycle) {
return result;
}
}
}
}
recStack.delete(id);
return { hasCycle: false };
};
for (const cat of this.categories) {
const result = hasCycle(cat.ID);
if (result.hasCycle) {
const cyclePath = this.getCyclePath(result.cycleStart);
this.showMessage(`Cyclic dependency detected: ${cyclePath.join(" -> ")}`, 'warning');
this.breakCycle(result.cycleStart);
break;
}
}
}
// Method to get the cycle path for displaying cyclic dependency
getCyclePath(startId) {
const path = [];
let currentId = startId;
do {
path.push(this.categories.find(cat => cat.ID === currentId).Name);
currentId = this.categories.find(cat => cat.ID === currentId).ParentID;
} while (currentId !== startId);
path.push(path[0]); // Return to the start of the cycle
return path;
}
// Method to break the cycle in case of cyclic dependency
breakCycle(startId) {
let currentId = startId;
let nextId;
do {
nextId = this.categories.find(cat => cat.ID === currentId).ParentID;
this.categories.find(cat => cat.ID === currentId).ParentID = null; // Break the cycle by removing the parent
currentId = nextId;
} while (currentId !== startId);
this.showMessage("Cyclic dependency automatically broken", 'info');
}
async toggleParentCategory(cat) {
const confirmToggle = confirm("Вы уверены, что хотите отменить родительскую категорию?");
if (confirmToggle) {
try {
const updatedCategory = {
...cat,
ParentID: null
};
const response = await fetch(`${this.apiUrl}/categories/${cat.ID}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updatedCategory),
});
this.logResponse(response, 'toggleParentCategory');
if (!response.ok) {
throw new Error('Не удалось отменить родительскую категорию');
}
// Обновляем локальный массив категорий
const index = this.categories.findIndex(c => c.ID === cat.ID);
if (index !== -1) {
this.categories[index] = {...this.categories[index], ParentID: null};
}
// Обновляем визуальное представление
this.$forceUpdate();
this.showMessage('Статус родительской категории успешно изменен', 'success');
} catch (error) {
console.error("Ошибка при отмене родительской категории:", error);
this.showMessage(`Ошибка при отмене родительской категории: ${error.message}`, 'error');
// Откатываем изменения в случае ошибки
await this.fetchCategories();
}
}
}
async toggleParentCategory(cat) {
const confirmToggle = confirm("Are you sure you want to remove the parent category?");
if (confirmToggle) {
try {
const updatedCategory = {
...cat,
ParentID: null
};
const response = await fetch(`${this.apiUrl}/categories/${cat.ID}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updatedCategory),
});
this.logResponse(response, 'toggleParentCategory');
if (!response.ok) {
throw new Error('Failed to remove parent category');
}
// Update the local categories array
const index = this.categories.findIndex(c => c.ID === cat.ID);
if (index !== -1) {
this.categories[index] = { ...this.categories[index], ParentID: null };
}
// Force update the UI
this.$forceUpdate();
this.showMessage('Parent category successfully removed', 'success');
} catch (error) {
console.error("Error removing parent category:", error);
this.showMessage(`Error removing parent category: ${error.message}`, 'error');
// Rollback changes in case of error
await this.fetchCategories();
}
}
}