fix: avoid duplicate backlog hydration load

This commit is contained in:
Janpeter Visser 2026-05-10 07:17:28 +02:00
parent b489e26665
commit 38d99834ef
5 changed files with 47 additions and 8 deletions

View file

@ -257,6 +257,35 @@ describe('selection cascade', () => {
expect(s.relations.taskIdsByStory).toEqual({})
expect(s.loading.loadedProductId).toBeNull()
})
it('setActiveProduct kan alleen context zetten zonder full backlog load', () => {
useProductWorkspaceStore.getState().hydrateSnapshot(
snapshotWith(
[makePbi({ id: 'p-1' })],
{ 'p-1': [makeStory({ id: 's-1', pbi_id: 'p-1' })] },
{ 's-1': [makeTask({ id: 't-1', story_id: 's-1' })] },
{ id: 'prod-1', name: 'Product 1' },
),
)
useProductWorkspaceStore.setState((s) => {
s.context.activePbiId = 'p-1'
s.context.activeStoryId = 's-1'
})
const fetchSpy = vi.spyOn(globalThis, 'fetch')
useProductWorkspaceStore
.getState()
.setActiveProduct(
{ id: 'prod-1', name: 'Product 1' },
{ load: false, preserveSelection: true },
)
const s = useProductWorkspaceStore.getState()
expect(fetchSpy).not.toHaveBeenCalled()
expect(s.context.activePbiId).toBe('p-1')
expect(s.context.activeStoryId).toBe('s-1')
expect(s.entities.pbisById['p-1']).toBeDefined()
})
})
// ─────────────────────────────────────────────────────────────────────────

View file

@ -151,6 +151,7 @@ export default async function ProductBacklogPage({ params, searchParams }: Props
<div className="flex-1 overflow-hidden">
<BacklogHydrationWrapper
productId={id}
productName={product.name}
initialData={{
pbis: pbis.map((p) => ({ id: p.id, code: p.code, title: p.title, priority: p.priority, sort_order: p.sort_order, description: p.description, created_at: p.created_at, status: pbiStatusToApi(p.status) })),
storiesByPbi,

View file

@ -91,6 +91,7 @@ export default async function MobileProductBacklogPage({ params, searchParams }:
<div className="flex flex-col h-full">
<BacklogHydrationWrapper
productId={id}
productName={product.name}
initialData={{
pbis: pbis.map((p) => ({ id: p.id, code: p.code, title: p.title, priority: p.priority, sort_order: p.sort_order, description: p.description, created_at: p.created_at, status: pbiStatusToApi(p.status) })),
storiesByPbi,

View file

@ -8,9 +8,11 @@ import { debugProps } from '@/lib/debug'
// De voorganger (stores/product-store.ts) wordt in Story 8 (T-876) verwijderd.
export function SetCurrentProduct({ id, name }: { id: string; name: string }) {
useEffect(() => {
useProductWorkspaceStore.getState().setActiveProduct({ id, name })
useProductWorkspaceStore
.getState()
.setActiveProduct({ id, name }, { load: false, preserveSelection: true })
return () => {
useProductWorkspaceStore.getState().setActiveProduct(null)
useProductWorkspaceStore.getState().setActiveProduct(null, { load: false })
}
}, [id, name])

View file

@ -78,7 +78,10 @@ interface State {
interface Actions {
hydrateSnapshot(snapshot: ProductBacklogSnapshot): void
setActiveProduct(product: ActiveProduct | null): void
setActiveProduct(
product: ActiveProduct | null,
options?: { load?: boolean; preserveSelection?: boolean },
): void
setActivePbi(pbiId: string | null): void
setActiveStory(storyId: string | null): void
setActiveTask(taskId: string | null): void
@ -223,15 +226,18 @@ export const useProductWorkspaceStore = create<ProductWorkspaceStore>()(
})
},
setActiveProduct(product) {
setActiveProduct(product, options) {
const requestId = newRequestId()
const productChanged = get().context.activeProduct?.id !== product?.id
const shouldResetSelection = productChanged || !options?.preserveSelection
set((s) => {
s.context.activeProduct = product
s.context.activePbiId = null
s.context.activeStoryId = null
s.context.activeTaskId = null
if (shouldResetSelection) {
s.context.activePbiId = null
s.context.activeStoryId = null
s.context.activeTaskId = null
}
s.loading.activeRequestId = requestId
if (productChanged) {
@ -252,7 +258,7 @@ export const useProductWorkspaceStore = create<ProductWorkspaceStore>()(
// selectie kan herstellen. T-857: restore-flow start na ensureProductLoaded.
writeProductHint(product?.id ?? null)
if (product) {
if (product && options?.load !== false) {
const productId = product.id
void (async () => {
await get().ensureProductLoaded(productId, requestId)