-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
- Check if updating to the latest Preact version resolves the issue
Describe the bug
This is a followup to another leak: VNode leak in Context · Issue #4905 · preactjs/preact. There is a VNode memory leak occurring when using Context Provider with state updates. After clicking a button that triggers a state change, the previous VNode hierarchy is still retained in memory, which can be observed in Chrome Dev Tools. This results in duplicate VNodes including 2 <button>
s, 2 <App>
s, 2 <Context>
s, 2 <RouterContent>
s and 2 Text nodes (both the old and new text content).
To Reproduce
StackBlitz reproduction link: https://stackblitz.com/edit/vitejs-vite-21mvd37v?file=src%2Fmain.tsx,index.html
Repro:
import { render, createContext } from 'preact';
import { useState, useContext } from 'preact/hooks';
const RouterContext = createContext(null);
function RouterContent() {
const { params, setRouterState } = useContext(RouterContext);
return (
<button onClick={() => setRouterState({ params: params + "!" })}>
{`No account found for username "${params}"`}
</button>
);
}
function App() {
const [routerState, setRouterState] = useState({ params: 'a' });
const contextValue = {
params: routerState.params,
setRouterState
};
return (
<RouterContext.Provider value={contextValue}>
<RouterContent />
</RouterContext.Provider>
);
}
render(<App />, document.getElementById('app'));
which compiles to (sometime its easier to check correctness of .js vs .ts):
import { jsx as _jsx } from "preact/jsx-runtime";
import { render, createContext } from 'preact';
import { useState, useContext } from 'preact/hooks';
const RouterContext = createContext(null);
function RouterContent() {
const { params, setRouterState } = useContext(RouterContext);
return (_jsx("button", { onClick: () => setRouterState({ params: params + "!" }), children: `No account found for username "${params}"` }));
}
function App() {
const [routerState, setRouterState] = useState({ params: 'a' });
const contextValue = {
params: routerState.params,
setRouterState
};
return (_jsx(RouterContext.Provider, { value: contextValue, children: _jsx(RouterContent, {}) }));
}
render(_jsx(App, {}), document.getElementById('app'));
Steps to reproduce the behavior:
- Run the web app (preferably with the previous memory leak patch applied -- I tested with the patch applied).
- Open Chrome Dev Tools and navigate to the Components tab (React DevTools extension)
- Click on the button that says
No account found for username "a"
- Observe in Dev Tools that previous VNode hierarchy is still retained alongside the new one
- See duplicate components and text nodes indicating a memory leak
Expected behavior
When the state updates and the component re-renders, the previous VNode hierarchy should be properly cleaned up and garbage collected. Only the current VNode tree should exist in memory, not duplicates of all components and text nodes from previous renders.


