Multi-page application errors with "Node.removeChild", memory leak in application?

Hello all,

I am in the process of transforming a plain Dash app into a multi-page Dash app. Here is how I’m setting up my app to get this done:

    dashboard_data_reader = SqlalchemyDashboardDataReader(SqlAlchemyDB(proj_path))

    """ Creating and setting up the Dash app """

    app = Dash(
        __name__,
        external_stylesheets=[theme_stylesheet],
        update_title=None,
        suppress_callback_exceptions=True,
    )
    app.title = f"Entropy - {project_name(proj_path)} [{project_path(proj_path)}]"

    app.layout = html.Div(
        [dcc.Location(id="url", refresh=False), html.Div(id="page-content")]
    )

    """ Registering callbacks used in pages """

    main_callbacks.register_callbacks(app, dashboard_data_reader)
    param_store_callbacks.register_callbacks(app, dashboard_data_reader)

    """ Callback to route between page layouts based on URL """

    @callback(Output("page-content", "children"), Input("url", "pathname"))
    def display_page(pathname):
        if pathname == "/":
            return main_layout.build_layout(proj_path, dashboard_data_reader)
        elif pathname == "/param_store":
            return param_store_layout.build_layout(proj_path, dashboard_data_reader)
        else:
            return "404"

You’ll notice that the registration of callbacks is done in functions imported from page-level modules. This is because the instantiation of my data source (dashboard_data_reader) is heavy and I’d rather cache it at the app level. Likewise layouts are imported from page-level modules.

When I run the app initial transitions between pages through NavLinks work fine. But after a few clicks I get an elaborate yet cryptic error message in browser window:


Node.removeChild: The node to be removed is not a child of this node



(This error originated from the built-in JavaScript code that runs Dash apps. Click to see the full stack trace or open your browser's console.)

removeChild@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:7724:20

unmountHostComponents@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:20584:22

commitDeletion@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:20635:28

commitMutationEffects@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:22917:27

callCallback@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:182:16

invokeGuardedCallbackDev@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:231:18

invokeGuardedCallback@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:286:33

commitRootImpl@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:22644:32

unstable_runWithPriority@http://localhost:8050/_dash-component-suites/dash/deps/react@16.v2_2_0m1646581418.14.0.js:2685:14

runWithPriority$1@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:11174:12

commitRoot@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:22516:22

finishSyncRender@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:21942:15

performSyncWorkOnRoot@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:21928:25

flushSyncCallbackQueueImpl/<@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:11224:26

unstable_runWithPriority@http://localhost:8050/_dash-component-suites/dash/deps/react@16.v2_2_0m1646581418.14.0.js:2685:14

runWithPriority$1@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:11174:12

flushSyncCallbackQueueImpl@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:11219:26

flushSyncCallbackQueue@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:11207:5

batchedUpdates$1@http://localhost:8050/_dash-component-suites/dash/deps/react-dom@16.v2_2_0m1646581418.14.0.js:21997:9

notify@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:85223:12

notifyNestedSubs@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:85293:15

handleChangeWrapper@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:85298:20

dispatch@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:86293:7

./node_modules/redux-thunk/es/index.js/createThunkMiddleware/middleware/</<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:85964:16

applyProps@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:1609:15

./src/observers/executedCallbacks.ts/observer/</<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:1650:40

forEach@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:76508:7

./node_modules/ramda/es/internal/_checkForMethod.js/_checkForMethod/<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:77185:119

f2@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:77418:14

./src/observers/executedCallbacks.ts/observer/<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:1637:58

forEach@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:76508:7

./node_modules/ramda/es/internal/_checkForMethod.js/_checkForMethod/<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:77185:119

f2@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:77418:14

observer@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:1619:54

./src/StoreObserver.ts/StoreObserver/</<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:415:9

forEach@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:76508:7

./node_modules/ramda/es/internal/_checkForMethod.js/_checkForMethod/<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:77185:119

f2@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:77418:14

./src/StoreObserver.ts/StoreObserver/<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:413:54

dispatch@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:86293:7

./node_modules/redux-thunk/es/index.js/createThunkMiddleware/middleware/</<@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:85964:16

_callee$@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:1854:25

u@http://localhost:8050/_dash-component-suites/dash_bootstrap_components/_components/dash_bootstrap_components.v1_0_3m1645349989.min.js:2:7133

e</c/s._invoke@http://localhost:8050/_dash-component-suites/dash_bootstrap_components/_components/dash_bootstrap_components.v1_0_3m1645349989.min.js:2:6922

e</w/</<@http://localhost:8050/_dash-component-suites/dash_bootstrap_components/_components/dash_bootstrap_components.v1_0_3m1645349989.min.js:2:7562

asyncGeneratorStep@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:1775:103

_next@http://localhost:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_2_0m1646581418.dev.js:17

When I look in the browser dev tools console I also see:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in s (created by s)
    in s (created by s)
    in div (created by s)
    in s (created by s)
    in s (created by s)
    in s (created by c)
    in Suspense (created by c)
    in c (created by CheckedComponent)
    in CheckedComponent (created by BaseTreeContainer)
    in ComponentErrorBoundary (created by BaseTreeContainer)
    in BaseTreeContainer (created by Context.Consumer)
    in Unknown (created by BaseTreeContainer)
    in div (created by Col)
    in Col (created by ea)
    in ea (created by CheckedComponent)
    in CheckedComponent (created by BaseTreeContainer)
    in ComponentErrorBoundary (created by BaseTreeContainer)
    in BaseTreeContainer (created by Context.Consumer)
    in Unknown (created by BaseTreeContainer)
    in div (created by Row)
    in Row (created by Xd)
    in Xd (created by CheckedComponent)
    in CheckedComponent (created by BaseTreeContainer)
    in ComponentErrorBoundary (created by BaseTreeContainer)
    in BaseTreeContainer (created by Context.Consumer)
    in Unknown (created by BaseTreeContainer) react-dom@16.v2_2_0m1646581418.14.0.js:82:32
    printWarning react-dom@16.v2_2_0m1646581418.14.0.js:82
    error react-dom@16.v2_2_0m1646581418.14.0.js:54
    warnAboutUpdateOnUnmountedFiberInDEV react-dom@16.v2_2_0m1646581418.14.0.js:23296
    scheduleUpdateOnFiber react-dom@16.v2_2_0m1646581418.14.0.js:21304
    enqueueSetState react-dom@16.v2_2_0m1646581418.14.0.js:12774
    setState react@16.v2_2_0m1646581418.14.0.js:557
    hideTooltipId index.tsx:31


Does my app really have memory leaks? Are they related to the way I am registering callbacks or rebuilding the layouts?

Can anyone share info on how I can debug this further?

Thanks,
urig

UPDATE:

I’ve found the root cause of the error. It’s in the content of my first page’s layout.

In my first page I have a DataTable with native filtering turned on (filter_action="native").

I also have a custom JS script that:
a. Locates the table cell that contains a specific table filter
b. Hides its contents and
c. Adds into the cell a new filter element that I’ve built using a dcc.Checklist.

Here’s the JS code:

const selector_for_success_filter_container = ".dash-filter.column-5";
const id_of_new_success_filter = "success-filter-checklist";

waitForElm(selector_for_success_filter_container).then((elm) => {
    reposition_success_filter()
});

function reposition_success_filter() {
    container = document.querySelector(selector_for_success_filter_container);
    if (container === null) {
        console.error("Couldn't find 'success' filter container")
    } else {
        hideChildren(container);
        new_filter = document.getElementById(id_of_new_success_filter);
        if (new_filter !== null) {
            container.appendChild(new_filter)
        }
    }
}

Commenting out the line where I append the new filter makes the error not occur.

My understanding now is that when I browse to a different page, the layout for the page I was in get removed from memory by React. When React tries to iterate over the HTML controls in the table cell that contains the filter it encounters my alien dcc.Checklist and this makes it fails with an error.

So now my question is - Is there a way for me to introduce my new filter into the table without “confusing” React’s “component clean-up” process?

Thanks in advance for any help offered.
urig

My understanding is that, in general, trying to make changes to the DOM outside of React is very likely to make things break. Once you’re using React, it’s in charge of managing the DOM, and any changes you make, React won’t be aware of, and things will no longer by synchronised.

I think you essentially have to find a way to do what you want using the DataTable component’s API. If the API doesn’t support want you want, you’d need to extend the DataTable by forking and rebuilding etc.

See the question about JQuery in the Dash FAQs. Essentially it’s the same thing but here with vanilla JavaScript.

1 Like