emilhe / dash-extensions

The dash-extensions package is a collection of utility functions, syntax extensions, and Dash components that aim to improve the Dash development experience
https://www.dash-extensions.com/
MIT License
418 stars 59 forks source link

MultiplexerTransform cannot read property error #69

Open jrkkfst opened 3 years ago

jrkkfst commented 3 years ago

I keep getting the error

Cannot read property 'children' of undefined when applying the MultiplexerTransform extension.

Unfortunately I cannot share a reproducible example at this time. I have tried creating the objects with defined children.

Any ideas of where to begin addressing this?

emilhe commented 3 years ago

What version are you on? Could you share the line where you construct the transform?

jrkkfst commented 3 years ago

I'm on 0.0.53.

app = DashProxy(
    transforms=[
        MultiplexerTransform(proxy_location=None),
        NoOutputTransform(),
    ],
)

It's for saving data in a dcc.Store and then retrieving it. And i Have multiple callbacks accessing the State of the Store object and then Also as Output.

I tried different thing with dcc.Store or just using a hidden html

It's a little complex because I have a class which creates the html objects and callbacks on launch, and then I create multiple objects of that class.

I am adding the callback without the decorator directly by calling app.callback, it hasn't worked with the decorator function.

Some code snippets:

This is the html object where I initialise the children as None. It is here I would expect if I didn't initialize it may cause error, but I set it.

html.Div(None, id=f"{self.id}_storage", style={"display": "none"})

How I create the callbacks

callback = self.make_callback(
   # function that returns the appropriate callback function
)
app.callback(
    Output(f"{self.id}_storage", "children"),
    [
        State(f"{self.id}_storage", "children"),
    ],
    prevent_initial_call=True,
)(callback)
jv1522 commented 3 years ago

I also faced this error. Can we have multiple dashh_extensions.enrich.Outputs in callbacks?

emilhe commented 3 years ago

@jv1522 yes that should be possible

emilhe commented 3 years ago

The error seems to indicate that the layout is not defined at the time where the multiplexer is called. I am not really sure how this can happen, but if you can share a reproducible example, I can try to address the problem. Until then, a possible workaround could be to "manually" place the proxy components in the layout, i.e.

proxy_container = html.Div()
app = DashProxy(transforms=[MultiplexerTransform(proxy_location=proxy_container)])

and then making sure that the proxy_container is added to the layout,

app.layout = html.Div([..., proxy_container])
jv1522 commented 3 years ago

Can we have dash extension's output and the plain dash's Output in the same callback, here is the code example In the below the duplicate_id is being used in another callback with dash extensions' Output

from dash.dependencies import Output
from dash_extensions.enrich import Output as Extended_Output

@app.callback(
    Output("unique_id1", "children"),
    Output("unique_id2", "data"),
    Extended_Output("duplicate_id", "children"),
    Input("id1", "value")
)
def do_something():
    pass
emilhe commented 3 years ago

I am not sure I understand why you don't just use the Output from dash_extensions.enrich for all the outputs? That would seem simpler

jrkkfst commented 3 years ago

The error seems to indicate that the layout is not defined at the time where the multiplexer is called. I am not really sure how this can happen, but if you can share a reproducible example, I can try to address the problem. Until then, a possible workaround could be to "manually" place the proxy components in the layout, i.e.

proxy_container = html.Div()
app = DashProxy(transforms=[MultiplexerTransform(proxy_location=proxy_container)])

and then making sure that the proxy_container is added to the layout,

app.layout = html.Div([..., proxy_container])

I still can't get it to work. I'm sorry I cannot easily provide a minimal working example.

It seems the proxy_container is not correctly populated even when I follow your example. First I initialize the app, then using my class create a number of objects with both html components and their callbacks, then I add those html components to my app.layout. It seems somehow the proxy_container isn't populated correctly.

Do I need to use the: from dash_extensions.enrich import Output ?

I think one key is that I define the proxy_container in the start, and then I create my objects using the classes I have, which makes the html components and callsback. At some point here the proxy_container should modified, and that modified proxy_container has to be fed into the final app.layout, as you suggest. However, because I'm using multiple python files, it is not obvious for me how to do the import of it back and forth to ensure, I then use the final version of the proxy_container.

jrkkfst commented 3 years ago

A key problem could be that I use a class to generate multiple different objects and then I assume that each of the instances of these objects may have added snippets to the proxy_container, and I'm not "saving" all those changes to the proxy_container and adding them "on top of" one another.

luggie commented 2 years ago

any news on this? I have the same behavior. Also for me it is very hard if not impossible to provide a minimal working example. I'm also adding the app.layout inside a class in another file as my DashProxy declaration. My callbacks are also spread across a lot of python files. unfortunately the error traceback doesn't provide help

TypeError: Cannot read properties of undefined (reading 'value')

at handleClientside (webpack://dash_renderer/./src/actions/callbacks.ts?:234:54)

at _callee$ (webpack://dash_renderer/./src/actions/callbacks.ts?:488:25)

at c (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-slider.js:1:109231)

at Generator._invoke (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-slider.js:1:109019)

at Generator.next (http://127.0.0.1:8050/_dash-component-suites/dash/dcc/async-slider.js:1:109656)

at asyncGeneratorStep (webpack://dash_renderer/./src/actions/callbacks.ts?:37:103)

at _next (webpack://dash_renderer/./src/actions/callbacks.ts?:39:194)

at eval (webpack://dash_renderer/./src/actions/callbacks.ts?:39:364)

at new Promise (<anonymous>)

at eval (webpack://dash_renderer/./src/actions/callbacks.ts?:39:97)
emilhe commented 2 years ago

The structure in terms of files shouldn't matter. But if you do some kind of dynamic modification of the app layout post load, it might. I don't see anything in the error message that seems to relate to the MultiplexerTransform, so I don't really have anything to go on in terms of debugging the issue.

If you can post a minimal example demonstrating the error, I can take a look.

jrkkfst commented 2 years ago

The problem still remains for me and I have to solve it in the future. It is still complex to make a simple minimal working example, but it seems that one of us may have to create one soon.

luggie commented 2 years ago

Yes same here, I cannot get my dash up running with creating those Store-trigger chains manually myself. When creating generic callbacks like in a loop, it scales quite dramatically :D I'll start to think about how to create a minimal example and post it soon

luggie commented 2 years ago

For my, the problem is component specific. Does the following example also through an error for you? It does for me, if the target component is a dcc.Dropdown, targeting its value.

from dash_extensions.enrich import MultiplexerTransform, DashProxy
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
from dash import html
from dash import dcc

app = DashProxy(__name__,
                transforms=[MultiplexerTransform()])

app.layout = \
    html.Div([
        dcc.Dropdown('dropdown-target', options=[{'label': 'option 1',  'value': 'option 1'},
                                                 {'label': 'option 2',  'value': 'option 2'}]),
        dbc.Button('Fire callback 1', id='btn-fire-callback-1'),
        dbc.Button('Fire callback 2', id='btn-fire-callback-2')
    ])

@app.callback(Output('dropdown-target', 'value'),
              Input('btn-fire-callback-1', 'n_clicks'), prevent_initial_call=True)
def _fire_callback_1(n):
    if n:
        return 'option 1'

@app.callback(Output('dropdown-target', 'value'),
              Input('btn-fire-callback-2', 'n_clicks'), prevent_initial_call=True)
def _fire_callback_1(n):
    if n:
        return 'option 2'

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

using: dash 2.0.0 das-extensions 0.0.65 dash-bootstrap-components 1.0.1

emilhe commented 2 years ago

No, the example works for me. I just tried creating a new venv from scratch installing only the packages you listed. For reference, I am OSX using Python 3.9.1

luggie commented 2 years ago

I use python 3.7.8 on Ubuntu 20.04. and anaconda 4.9.2

emilhe commented 2 years ago

Could you try with 3.9? And maybe without anaconda (I doubt it's related, but I don't use it)? (just the small example, so that we can locate the origin of the error)? Altså, I am testing with Chrome browser.

luggie commented 2 years ago

yep, my minimal example for me works on python 3.9.1 (on a fresh anaconda env with just the needed packages). I'll confirm again, if this also works with my full dash app

emilhe commented 2 years ago

Great. Let me know how it goes. If it works out, I'll add a note in the docs that Python 3.9 is recommended.

luggie commented 2 years ago

Now it 'sort of works'. However, it feels very unstable. Here's why: Before I heard about your MultiplexerTransform, I used to solve multiple outputs for components just like you did it in your extension: with chains of triggers 'funneling' many callbacks to one output. All manually. Now, whenever I 'free' one of those trigger funnels and 'release' two or more callbacks to output to the same component, I end up with the same error, I posted earlier. I can resolve it when I stop the dash app, clean my browser's cache and restart the app again. Hence dash loses its ability to hot-reload after changes which is quite annoying and doesn't feel save to be honest. Is this behavior expected or something that is easy to explain?

emilhe commented 2 years ago

It don't see why it should be be necessary to clear any cache. But if you do any modifications that change the component structure of the app, you might need to do a full refresh (e.g. app restart + browser reload, i.e. F5 or so) of the app to ensure that the auto generated multiplexing elements are created properly. I haven't worked much with hot reload, so I am not sure how well it integrates.

emilhe commented 2 years ago

In terms of "feeling safe", I would generally think that using a standardised implementation (like the one here) would be less error prone (and easier to maintain) as compared to manually implementing the logic each time. I am not saying it's error free, but I (and others) have used it across a number of apps without issues.

luggie commented 2 years ago

It don't see why it should be be necessary to clear any cache. But if you do any modifications that change the component structure of the app, you might need to do a full refresh (e.g. app restart + browser reload, i.e. F5 or so) of the app to ensure that the auto generated multiplexing elements are created properly. I haven't worked much with hot reload, so I am not sure how well it integrates.

Yes the hot reloading does not integrate fully. The only difference between the behavior of Dash and DashProxy is the following: Like I stated before, my app has loads of self made trigger chains that I now 'free' one-by-one using your extension. Every time I do so, I need to restart the page (even if app.run_server(debug=True) is set) to get the changes applied. Otherwise I get the error message, posted earlier. The hot reloading itself works. When I change anything that does not regard the functionality of MultiplexerTransform, changes in the layout or with callbacks are accepted right away. Maybe this helps if you are keen to trace this down. I'd be happy to help you with that, if you can think of any more information that you might need.

In terms of "feeling safe", I would generally think that using a standardised implementation (like the one here) would be less error prone (and easier to maintain) as compared to manually implementing the logic each time. I am not saying it's error free, but I (and others) have used it across a number of apps without issues.

99% agree. The only thing that is annoying, is that one does not get any useful information from error messages that simply say that "something went wrong in the JS backend". That being said: Thanks heaps for your effort in creating this extension and the instant support!