Using Dash table on multipage app leads to warning

Hi,
I am currently trying to create a multipage app including a dash table and am experiencing weird behaviour of my app.

Whenever I switch between the pages of my app a couple of times I get the following warning by my browser (Firefox):

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 l (created by s)
    in s (created by u)
    in div (created by u)
    in u (created by c)
    in c (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)

I reduced my app to the point that I used an example from the plotly documentation to which I only added a dash table. I did not change anything else. The warning still appears.

I used the example given in the section “Dynamically Create a Layout for Multi-Page App Validation” from https://dash.plotly.com/urls

Can anyone help me with that?

from dash import Dash, html, dcc, Input, Output, State, callback, dash_table
import pandas as pd

# create example data as dict
data = {
  "numbers": [1,2,3,4,5,6,7,8,9],
  "cities": ["Frankfurt", "Kassel", "Darmstadt", "Wiesbaden", "Gießen", "Fulda", "Hanau", "Dieburg", "Limburg"],
}

# load data into a DataFrame object
df = pd.DataFrame(data)

app = Dash(__name__)

url_bar_and_content_div = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])

layout_index = html.Div([
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_1 = html.Div([
    html.H2('Page 1'),
    dcc.Input(id='input-1-state', type='text', value='Montreal'),
    dcc.Input(id='input-2-state', type='text', value='Canada'),
    

    # add dash table to page 1
    dash_table.DataTable(
        df.to_dict('records'), [{"name": i, "id": i} for i in df.columns],
        page_size=6,
        style_table={"overflowX": "scroll"},
        id="dash-data-table",
        sort_action="native",
    ),
    
    html.Button(id='submit-button', n_clicks=0, children='Submit'),
    html.Div(id='output-state'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_2 = html.Div([
    html.H2('Page 2'),
    dcc.Dropdown(['LA', 'NYC', 'MTL'], 'LA', id='page-2-dropdown'),
    html.Div(id='page-2-display-value'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
])

# index layout
app.layout = url_bar_and_content_div

# "complete" layout
app.validation_layout = html.Div([
    url_bar_and_content_div,
    layout_index,
    layout_page_1,
    layout_page_2,
])


# Index callbacks
@callback(Output('page-content', 'children'),
              Input('url', 'pathname'))
def display_page(pathname):
    if pathname == "/page-1":
        return layout_page_1
    elif pathname == "/page-2":
        return layout_page_2
    else:
        return layout_index


# Page 1 callbacks
@callback(Output('output-state', 'children'),
              Input('submit-button', 'n_clicks'),
              State('input-1-state', 'value'),
              State('input-2-state', 'value'))
def update_output(n_clicks, input1, input2):
    return f'The Button has been pressed {n_clicks} times. \
            Input 1 is {input1} and Input 2 is {input2}'


# Page 2 callbacks
@callback(Output('page-2-display-value', 'children'),
              Input('page-2-dropdown', 'value'))
def display_value(value):
    return f'You have selected {value}'


if __name__ == '__main__':
    app.run_server(debug=True)

Hello @Max3,

I believe that this is due to the nature of the application works. When a page loads, it will send callbacks to the server, if you change pages before there is a response received, then the browser tries to update information that is not currently present in the application.

Also, is there a reason why you are using the index instead of Dash pages?

Thanks @jinnyzor for your reply.

You said that this might be due to the nature of the application. Is that a bad thing? Can I just ignore that warning?

I am using the index because I dont want my page content to be put into “hmtl.Div” but “dbc.Container”. Html.Div and all other html-elements from dash include event listeners which are read by screen readers (and other assistive technology) as clickable elements even though there is nothing to click and no interactivity intended:
events

When using dbc.Container I get divs without event listeners. I am developing my app under the condition that there are as little accessibility issues as possible. If you know a better way, let me know.

Yes, I believe that you can ignore this error, as you are expecting it if you switch pages too quickly. What you can do is add prevent_initial_call to anything that doesnt need to be updated immediately, which should probably be everything. :laughing:

Good to know about the dbc.Container and html.Div.

Hi @Max3

Thanks for reporting the issue with event listeners and screen readers.

I just did a pull request to make it possible to remove event listeners from html components. You can follow the progress here: added static prop to remove event listener by AnnMarieW · Pull Request #2370 · plotly/dash · GitHub

This will make it possible to use Pages without the accessibility warning. :tada:

2 Likes

Thank you very much @AnnMarieW ! That is something I really need. Could you explain how I would be using the “static” prop when using pages?

Lets say I use the following example from the docs:

from dash import Dash, html, dcc, callback
import dash

app = Dash(__name__, use_pages=True)

dash.register_page("home",  path='/', layout=html.Div('Home Page'))
dash.register_page("analytics", layout=html.Div('Analytics'))

app.layout = html.Div([
    html.Div(
        [
            html.Div(
                dcc.Link(
                    f"{page['name']} - {page['path']}", href=page["relative_path"]
                )
            )
            for page in dash.page_registry.values()
        ]
    ),
    dash.page_container,
])


if __name__ == '__main__':
    app.run_server(debug=True)

There are various html.Divs which where I could easily add the “static” prop. So far I just changed them to dbc.Containers to get rid of the events listeners. There are however some more divs created by pages which are not visible in the code. These lead to the following:

eventsannmarie

How would I address those divs?

Hello @Max3,

This change hasn’t been implemented yet, but you can track the process on the link that @AnnMarieW posted. I think the goal is to remove the listener from the overall _pages_content div and allow the user to determine whether or not an element is static.

You can check out more if you go to the link.

1 Like

Good news! :tada:

The pull request to make Dash more screen-reader friendly has been approved and will be in the next release (2.7.2)

There is a new disable_n_clicks prop in all html components, giving the ability to remove the event listener that interferes with screen readers.

By default, n_clicks will be enabled on any html component that has an id. If n_clicks is not used in a callback for that component, the event listener can be removed by setting disable_n_clicks=True`

For example:

  • No event listener:
    html.Div()
    html.Div(id="my-id", disable_n_clicks=True)
  • onClick event listener:
    html.Div(id="my-id")
1 Like

Hello again,

I tried the new disable_n_clicks prop on different htm components and it works fine. When it comes to using pages however I am not getting farther than I did before. Maybe I am missing something.

I tried the following app:

-- app.py --

import dash
from dash import html, dcc, Input, Output
import dash_bootstrap_components as dbc

app = dash.Dash(__name__, use_pages=True)

app.layout = dbc.Container(
    [
        # main app framework
        dbc.Container("Python Multipage App with Dash", style={'fontSize':50, 'textAlign':'center'}),
        dbc.Container([
            dcc.Link(page['name']+"  |  ", href=page['path'])
            for page in dash.page_registry.values()
        ]),
        
        # content of each page
        dash.page_container,
         
    ]
)
-- page1.py --

import dash
from dash import html

dash.register_page(__name__, path='/')

layout = html.Div(
    [
        html.P('There is some text.')
    ],
        
)

The html.P does not get an event listeners as it does not have an id. Everything fine there. Pages however creates two divs with ids automatically (_pages_content, _pages_dummy). Is it possible to insert the disable_n_clicks in those too?

divs

Thank you very much

Hi @Max3

Darn, I intended to include that with the pull request. Unfortunately it will take another pull request to fix. Hopefully, that will make it into a patch release (2.8.1) soon rather than waiting for 2.9.0.

Thank you for reporting.

The patch release (2.8.1) is now available and works fine! Using pages does not lead to any accessibility issues anymore :slight_smile:

1 Like