plotly / dash

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

[BUG] TypeError: Cannot read properties of undefined (reading 'concat') in getInputHistoryState #2916

Closed wKollendorf closed 3 months ago

wKollendorf commented 4 months ago

Context The following code is a simplified example of the real application. The layout initially contains a root div and inputs. By using pattern matching and set_props the callback function can be setup in a general manner. Anyway, when triggering the callback by a button the root div will be populated by any other content like another div and more inputs. The newly created inputs then trigger the same callback and the layout is further updated. A nested structure.

The ID is like this:{'type': 'ELEMENT_TYPE', 'eid': 'ELEMNET_ID, 'target': TARGET_ID'}

In the following example works like this:

  1. Button1 is clicked
  2. Callback update is triggered
  3. key target is checked
  4. run set_props on the element with eid = target and the desired content

failing example: example

This works for Button1 as expected. But when clicking Button2 (which was created by Button1) the mentioned TypeError occures. When inspecting the console, it can be seen, that the itempath is undefined.

from dash import Dash, html, dcc, ALL, callback_context, exceptions, set_props, callback
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

# app
app = Dash(__name__)

# layout
app.layout = html.Div([
    html.H4(['Title'], id={'type': 'H4','eid': 'title', 'target': ''})
    ,dbc.Button(['BTN1 (create new div)'], id={'type': 'Button','eid': 'btn1', 'target': 'content1'})
    ,html.Div([], id={'type': 'Div','eid': 'content1', 'target': ''})
    ,html.Div([], id={'type': 'Div','eid': 'content3', 'target': ''})
], id={'type': 'Div','eid': 'body', 'target': ''})

#callback
@callback(
    Output({'type': 'H4', 'eid': 'title', 'target': ''}, 'children')
    ,State({'type': 'Div', 'eid': 'body', 'target': ''}, 'children')
    ,Input({'type': 'Button', 'eid': ALL, 'target': ALL}, 'n_clicks')
    ,Input({'type': 'Input', 'eid': ALL, 'target': ALL}, 'value')
    ,prevent_initial_call=True
)
def update(
    H4__children
    ,Button__n_clicks
    ,Input__value
):
    if callback_context.triggered_id is not None:
        id_changed = callback_context.triggered_id
    else:
        raise exceptions.PreventUpdate

    if id_changed['target'] == 'content1':
        set_props(
            {'type': 'Div', 'eid': id_changed.target, 'target': ''}
            ,{'children': [
                    dbc.Button(['BTN2 (write OUTPUT to new div)'], id={'type': 'Button','eid': 'btn2', 'target': 'content2'})
                    #dbc.Button(['BTN2 (write OUTPUT to new div)'], id={'type': 'Button','eid': 'btn2', 'target': 'content3'})
                    ,html.Div(['div content2 from button1'], id={'type': 'Div','eid': 'content2', 'target': ''})
             ]}
        )

    elif id_changed['target'] == 'content2':
        set_props({'type': 'Div', 'eid': id_changed.target, 'target': ''}, {'children': 'OUTPUT for content2 from button2'})

    elif id_changed['target'] == 'content3':
        set_props({'type': 'Div', 'eid': id_changed.target, 'target': ''}, {'children': 'OUTPUT for content3 from button2'})        
    return 'Title'

# main
if __name__ == '__main__':
    app.run(debug=True, dev_tools_hot_reload=True, dev_tools_ui=True)

Changing the target of Button1 to content3 works (just replace the line for dbc:Button within the callback). So the itempath for Button2 is found and the callback for the new input works.

working example: fail

pip list | grep dash

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-table                5.0.0

Describe the bug The itempath for content2 Div is undefined and concat in getInputHistoryState fails. The itempath is undefined because the component is not present within the store. So it looks like set_props does not update the store?

reducer.js:57 Uncaught (in promise) 
TypeError: Cannot read properties of undefined (reading 'concat')
    at getInputHistoryState (reducer.js:57:36)
    at reducer.js:82:34
    at reducer.js:121:16
    at dispatch (redux.js:288:1)
    at index.js:20:1
    at dispatch (redux.js:691:1)
    at callbacks.ts:214:9
    at index.js:16:1
    at dispatch (redux.js:691:1)
    at callbacks.ts:255:13

Expected behavior

The message 'OUTPUT for content2 from button2' should appear in Div with id {'type': 'Div','eid': 'content2', 'target': ''}.

wKollendorf commented 4 months ago

Just learnd, that set_props in a callback only modifies the properties of components on the client side. Also new components are only available on the client side. So this issue can be closed. Anyway, is there a way to make set_props work on server side too?

gvwilson commented 3 months ago

Hi - I am closing this as a bug report but @Coding-with-Adam is there a way to make set_props work on the server side?