fix request cancellation issue in batch add weapons/summons

The addWeapons/addSummons methods were using Promise.all with Array.fill()
which created arrays where all elements referenced the same object. This
caused the request deduplication logic in BaseAdapter to cancel previous
requests since they all had the same body/requestId.

Fix:
- Use Array.from() with spread to create unique object instances
- Execute requests sequentially to avoid deduplication conflicts
- Improve error handling in AddToCollectionModal to filter CancelledErrors
This commit is contained in:
Justin Edmund 2025-12-03 07:37:03 -08:00
parent d9dd8f58ee
commit c37c4f0101
2 changed files with 58 additions and 33 deletions

View file

@ -203,13 +203,19 @@ export class CollectionAdapter extends BaseAdapter {
inputs: Array<CollectionWeaponInput & { quantity?: number }>
): Promise<CollectionWeapon[]> {
// Expand inputs based on quantity
// Note: We create individual objects to ensure unique request IDs for deduplication
const expanded = inputs.flatMap((input) => {
const count = input.quantity ?? 1
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { quantity, ...rest } = input
return Array(count).fill(rest) as CollectionWeaponInput[]
return Array.from({ length: count }, () => ({ ...rest })) as CollectionWeaponInput[]
})
return Promise.all(expanded.map((input) => this.addWeapon(input)))
// Execute sequentially to avoid request deduplication issues
const results: CollectionWeapon[] = []
for (const input of expanded) {
results.push(await this.addWeapon(input))
}
return results
}
/**
@ -282,13 +288,19 @@ export class CollectionAdapter extends BaseAdapter {
inputs: Array<CollectionSummonInput & { quantity?: number }>
): Promise<CollectionSummon[]> {
// Expand inputs based on quantity
// Note: We create individual objects to ensure unique request IDs for deduplication
const expanded = inputs.flatMap((input) => {
const count = input.quantity ?? 1
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { quantity, ...rest } = input
return Array(count).fill(rest) as CollectionSummonInput[]
return Array.from({ length: count }, () => ({ ...rest })) as CollectionSummonInput[]
})
return Promise.all(expanded.map((input) => this.addSummon(input)))
// Execute sequentially to avoid request deduplication issues
const results: CollectionSummon[] = []
for (const input of expanded) {
results.push(await this.addSummon(input))
}
return results
}
/**

View file

@ -219,42 +219,55 @@
}
async function handleAdd() {
// Capture selected data before any state changes
const currentEntityType = entityType
const characterInputs =
currentEntityType === 'character'
? Array.from(selectedIds).map((characterId) => ({
characterId,
uncapLevel: 4,
transcendenceStep: 0
}))
: []
const weaponInputs =
currentEntityType === 'weapon'
? Array.from(selectedQuantities.entries()).map(([weaponId, quantity]) => ({
weaponId,
quantity,
uncapLevel: 3,
transcendenceStep: 0
}))
: []
const summonInputs =
currentEntityType === 'summon'
? Array.from(selectedQuantities.entries()).map(([summonId, quantity]) => ({
summonId,
quantity,
uncapLevel: 3,
transcendenceStep: 0
}))
: []
try {
if (entityType === 'character') {
if (selectedIds.size === 0) return
const inputs = Array.from(selectedIds).map((characterId) => ({
characterId,
uncapLevel: 4,
transcendenceStep: 0
}))
await addCharacterMutation.mutateAsync(inputs)
} else if (entityType === 'weapon') {
if (selectedQuantities.size === 0) return
const inputs = Array.from(selectedQuantities.entries()).map(([weaponId, quantity]) => ({
weaponId,
quantity,
uncapLevel: 3,
transcendenceStep: 0
}))
await addWeaponMutation.mutateAsync(inputs)
if (currentEntityType === 'character') {
if (characterInputs.length === 0) return
await addCharacterMutation.mutateAsync(characterInputs)
} else if (currentEntityType === 'weapon') {
if (weaponInputs.length === 0) return
await addWeaponMutation.mutateAsync(weaponInputs)
} else {
if (selectedQuantities.size === 0) return
const inputs = Array.from(selectedQuantities.entries()).map(([summonId, quantity]) => ({
summonId,
quantity,
uncapLevel: 3,
transcendenceStep: 0
}))
await addSummonMutation.mutateAsync(inputs)
if (summonInputs.length === 0) return
await addSummonMutation.mutateAsync(summonInputs)
}
// Close modal after successful mutation
open = false
onOpenChange?.(false)
} catch (error) {
console.error(`Failed to add ${entityNames[entityType].plural}:`, error)
// Only log non-cancellation errors
if (error && typeof error === 'object' && 'name' in error && error.name !== 'CancelledError') {
console.error(`Failed to add ${entityNames[currentEntityType].plural}:`, error)
}
}
}