plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
20.84k stars 2.01k forks source link

[BUG] Layout as list of components does not work when layout is a function #2905

Open antonymilne opened 4 days ago

antonymilne commented 4 days ago

Describe your context

dash                       2.17.1
dash_ag_grid               31.2.0
dash-bootstrap-components  1.6.0
dash-core-components       2.0.0
dash-html-components       2.0.0
dash-mantine-components    0.12.1
dash-table                 5.0.0
dash-testing-stub          0.0.2

Describe the bug

https://github.com/plotly/dash/pull/2795 enabled you to pass a list of components to app.layout. However, this does not work when app.layout is set to a function:

import datetime

from dash import Dash, html

def layout():
    return [html.H1(datetime.datetime.now())]

app = Dash(__name__)
app.layout = layout

app.run()

Gives:

Traceback (most recent call last):
  File "/Users/antony_milne/Library/Application Support/JetBrains/PyCharm2024.1/scratches/scratch_51.py", line 11, in <module>
    app.layout = layout
    ^^^^^^^^^^
  File "/Users/antony_milne/Library/Application Support/hatch/env/virtual/vizro/fm3ubPZu/vizro/lib/python3.11/site-packages/dash/dash.py", line 734, in layout
    [simple_clone(c) for c in layout_value._traverse_ids()],
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute '_traverse_ids'

I see that the main motivation for #2795 was that it simplified examples for beginners, so this is not such an important case because using a function for layout is already more advanced. But still I think this should probably work.

The root of the problem is that the code in Dash.layout that handles the case that layout is a function still expect it to return a single Dash component. One possible fix would be to alter this line: https://github.com/plotly/dash/blob/bbd013cd9d97d8c41700b240dd95c32e6b875998/dash/dash.py#L729 This expects a single Dash component to be returned rather than a list so it works if you just wrap it in an html.Div.