{"record":{"id":"3249622668680ab0","repo":"remix-run/remix","slug":"render-called-after-component-was-removed-potenti","errorCode":null,"errorMessage":"render called after component was removed, potential application memory leak","messagePattern":"render called after component was removed, potential application memory leak","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/ui/src/runtime/component.ts","lineNumber":298,"sourceCode":"  #scheduleUpdate = (): void => {\n    let queue = this.#updateQueue\n    if (!queue) throw new Error('scheduleUpdate not implemented')\n    let vnode = this.#updateVNode\n    let domParent = this.#updateDomParent\n    if (!vnode || !domParent) throw new Error('scheduleUpdate target not initialized')\n    queue.enqueue(vnode, domParent)\n  }\n  #tasks: Task[] = []\n\n  constructor(config: ComponentConfig) {\n    this.#config = config\n    this.frame = config.frame\n    this.#handle = this.#createHandle()\n  }\n\n  render = (nextProps: ElementProps): [RemixNode, Array<() => void>] => {\n    if (this.#removed) {\n      console.warn('render called after component was removed, potential application memory leak')\n      return [null, []]\n    }\n\n    this.#abortRenderSignal()\n    syncProps(this.#props, nextProps)\n\n    let renderFn = this.#renderFn\n\n    if (renderFn === undefined) {\n      let initialize = this.#config.type as unknown as (handle: Handle<ElementProps, C>) => unknown\n      let result = initialize(this.#handle)\n\n      if (!isRenderFn(result)) {\n        let name = this.#config.type.name || 'Anonymous'\n        throw new Error(`${name} must return a render function, received ${typeof result}`)\n      }\n\n      renderFn = result","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/ui/src/runtime/component.ts#L280-L316","documentation":"The UI component runtime warns when render() is invoked on a component that was already removed from the tree. This usually indicates a retained reference to a removed component whose render is still being scheduled, which leaks memory and does work that is discarded.","triggerScenarios":"Holding onto a component runtime handle (or a stale scheduler callback) after the component unmounted, then calling render(nextProps) on it.","commonSituations":"Missing cleanup in effects/subscriptions that capture the component; scheduler queues not flushed or cancelled on unmount; hydration regions replaced while queued renders remain.","solutions":["Cancel pending render schedules in the component's cleanup/unmount path","Audit subscriptions and timers that capture the component and close over it after removal","Return early when the component's owning region is unmounted"],"exampleFix":"// before\nconst interval = setInterval(() => component.render(props), 1000)\n// after\nconst interval = setInterval(() => { if (!removed) component.render(props) }, 1000)\nclearInterval(interval) // on cleanup","handlingStrategy":"validation","validationCode":"if (!component.isRemoved?.()) component.render(props)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Clear timers/subscriptions that capture component handles on unmount","Cancel pending scheduler callbacks in cleanup paths"],"tags":["ui","memory-leak","render-lifecycle"],"backgroundTag":"render-after-unmount","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}