fix(compliance-scope): Race Condition bei State-Persistenz beheben
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 35s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 20s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 35s
CI / test-python-backend-compliance (push) Successful in 34s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 20s
SDK Context laedt State asynchron vom Server. Die Page las bei Mount sdkState.complianceScope (noch null), fiel auf leeres localStorage zurueck, und der Save-Effect ueberschrieb dann den echten State mit leeren Daten. Fix: sdkState.complianceScope wird jetzt reaktiv beobachtet, und leere States werden nie zurueckgeschrieben. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback, useMemo } from 'react'
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
||||||
import { useSDK } from '@/lib/sdk'
|
import { useSDK } from '@/lib/sdk'
|
||||||
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader/StepHeader'
|
import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader/StepHeader'
|
||||||
import {
|
import {
|
||||||
@@ -53,36 +53,41 @@ export default function ComplianceScopePage() {
|
|||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [isEvaluating, setIsEvaluating] = useState(false)
|
const [isEvaluating, setIsEvaluating] = useState(false)
|
||||||
|
|
||||||
|
// Guard against save-loop: tracks whether we're syncing FROM SDK → local state
|
||||||
|
const syncingFromSdk = useRef(false)
|
||||||
|
|
||||||
// Regulation assessment state
|
// Regulation assessment state
|
||||||
const [applicableRegulations, setApplicableRegulations] = useState<ApplicableRegulation[]>([])
|
const [applicableRegulations, setApplicableRegulations] = useState<ApplicableRegulation[]>([])
|
||||||
const [supervisoryAuthorities, setSupervisoryAuthorities] = useState<SupervisoryAuthorityInfo[]>([])
|
const [supervisoryAuthorities, setSupervisoryAuthorities] = useState<SupervisoryAuthorityInfo[]>([])
|
||||||
const [regulationAssessmentLoading, setRegulationAssessmentLoading] = useState(false)
|
const [regulationAssessmentLoading, setRegulationAssessmentLoading] = useState(false)
|
||||||
|
|
||||||
// Load from SDK context first (persisted via State API), then localStorage as fallback.
|
// Sync from SDK context when it becomes available (handles async loading).
|
||||||
// Runs ONCE on mount only — empty deps breaks the dispatch→sdkState→setScopeState→dispatch loop.
|
// The SDK context loads state from server/localStorage asynchronously, so
|
||||||
|
// sdkState.complianceScope may arrive AFTER this page has already mounted.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
const ctxScope = sdkState.complianceScope
|
||||||
// Priority 1: SDK context (loaded from PostgreSQL via State API)
|
if (ctxScope && ctxScope.answers?.length > 0) {
|
||||||
const ctxScope = sdkState.complianceScope
|
syncingFromSdk.current = true
|
||||||
if (ctxScope && ctxScope.answers?.length > 0) {
|
setScopeState(ctxScope)
|
||||||
setScopeState(ctxScope)
|
setIsLoading(false)
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(ctxScope))
|
} else if (isLoading) {
|
||||||
} else {
|
// SDK has no scope data — try localStorage fallback, then give up
|
||||||
// Priority 2: localStorage fallback
|
try {
|
||||||
const stored = localStorage.getItem(STORAGE_KEY)
|
const stored = localStorage.getItem(STORAGE_KEY)
|
||||||
if (stored) {
|
if (stored) {
|
||||||
const parsed = JSON.parse(stored) as ComplianceScopeState
|
const parsed = JSON.parse(stored) as ComplianceScopeState
|
||||||
setScopeState(parsed)
|
if (parsed.answers?.length > 0) {
|
||||||
dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: parsed })
|
setScopeState(parsed)
|
||||||
|
dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: parsed })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load compliance scope from localStorage:', error)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load compliance scope state:', error)
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [sdkState.complianceScope])
|
||||||
|
|
||||||
// Fetch regulation assessment if decision exists on mount
|
// Fetch regulation assessment if decision exists on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -92,9 +97,14 @@ export default function ComplianceScopePage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isLoading])
|
}, [isLoading])
|
||||||
|
|
||||||
// Save to localStorage and SDK context whenever state changes
|
// Save to localStorage and SDK context whenever LOCAL state changes (user edits).
|
||||||
|
// Guarded: don't save empty state, and don't echo back what we just loaded from SDK.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading) {
|
if (!isLoading && scopeState.answers.length > 0) {
|
||||||
|
if (syncingFromSdk.current) {
|
||||||
|
syncingFromSdk.current = false
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(scopeState))
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(scopeState))
|
||||||
dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: scopeState })
|
dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: scopeState })
|
||||||
|
|||||||
Reference in New Issue
Block a user