{"record":{"id":"7bad00447dd7f1c3","repo":"siyuan-note/siyuan","slug":"view-state-service-has-been-destroyed","errorCode":null,"errorMessage":"View state service has been destroyed","messagePattern":"View state service has been destroyed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/util/viewState.ts","lineNumber":260,"sourceCode":"\n    private scheduleFlush() {\n        this.clearFlushTimer();\n        this.flushTimer = setTimeout(() => {\n            this.flushTimer = undefined;\n            this.flush().catch((error) => console.error(error));\n        }, this.flushDelay);\n    }\n\n    private clearFlushTimer() {\n        if (this.flushTimer !== undefined) {\n            clearTimeout(this.flushTimer);\n            this.flushTimer = undefined;\n        }\n    }\n\n    private ensureActive() {\n        if (this.destroyed) {\n            throw new Error(\"View state service has been destroyed\");\n        }\n    }\n}\n","sourceCodeStart":242,"sourceCodeEnd":264,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/app/src/util/viewState.ts#L242-L264","documentation":"ViewStateService.ensureActive throws this when set() or patch() is called after the service has been destroyed (destroy() was invoked and its flush completed). The service intentionally refuses new mutations because its pending buffers and timer are torn down and writes would be silently lost.","triggerScenarios":"Holding a reference to a ViewStateService after calling destroy() and then invoking set() or patch(). Typical in editors: the view closes and destroys its service, but an in-flight async callback, event handler, or debounced update still fires against the old instance.","commonSituations":"Tab/window closed or view re-created while a setTimeout/await continuation still calls set(); an event listener not unbound on view close; a component re-render racing with destroy during layout changes.","solutions":["Stop using the service after destroy(): guard call sites with a check that the owning view is still open","Unbind event listeners and cancel timers/promises that reference the service in the destroy path","If the code may legitimately outlive one instance, create a fresh ViewStateService (same identity) instead of reusing the destroyed one","Ignore or downgrade the thrown error when the write is fire-and-forget best-effort UI state"],"exampleFix":"// before\nsetTimeout(() => service.set(\"scroll\", pos), 500); // service may be destroyed by then\n// after\nconst timer = setTimeout(() => {\n    if (!isViewClosed) service.set(\"scroll\", pos);\n}, 500);\nonDestroy(() => clearTimeout(timer));","handlingStrategy":"try-catch","validationCode":"let active = false;\ntry {\n    service.set(\"scroll\", pos); // throws if destroyed\n    active = true;\n} catch {\n    // service destroyed; skip the write\n}","typeGuard":"const isUsable = (service: ViewStateService | undefined | null): service is ViewStateService =>\n    service instanceof ViewStateService; // pair with an isDestroyed flag maintained by the owner\n// owner-side guard:\nconst canWrite = (service: ViewStateService | undefined) => service !== undefined && !destroyed;","tryCatchPattern":"try {\n    service.set(field, value);\n} catch (error) {\n    if (error instanceof Error && error.message === \"View state service has been destroyed\") {\n        return; // expected during teardown; ignore best-effort state writes\n    }\n    throw error;\n}","preventionTips":["Null out the service reference when the view is destroyed so stale callbacks cannot reach it","Cancel timers, promises, and event listeners in the destroy path before calling destroy()","Route all writes through one owner method that checks an isClosed flag","Never cache the service in module-level variables shared across view lifecycles"],"tags":["view-state","lifecycle","use-after-destroy","frontend"],"backgroundTag":"invalid-state-transition","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}