šŸ“£ Dash v1.11.0 Release - Introducing Pattern-Matching Callbacks

Canā€™t edit my comment after a certain time, so hereā€™s what Iā€™ve done instead based on what @alexcjohnson recommended if it can help anyone.

  • For the custom view depending on the user: always registering the callback and returning either the componentā€™s layout or an empty layout ("" for example) actually works. It would just be like
child1.py

def layout():
    ### insert some layout here


# Callbacks 
@app.callback(Output('foo', 'foo_prop'), [Input('bar', 'bar_prop')])
def my_cb(value):
   ###
   # some logic here
   return new_foo_prop_value

####
parent.py

from children import child1
from flask_login import current_user

@app.callback(
   Output('child-container', 'children'),
   [Input('hidden-div-trigger', 'children')])
def render_child(_):
   if current_user.role <= child1.allowed_role:
       return child1.layout()
   else:
       return ""
  • Iā€™ve used the pattern matching in an admin panel to render a user management form which has three possible ā€œactionsā€: add (add a new user), edit (edit userā€™s username, email etc but not password), update (update password). Each action would correspond to a different version of the form layout.

Thereā€™s always only one form existing, it can just take several layouts. So all my components have a fixed index (0 here). But using the pattern matching prevents bugs where one of the input doesnā€™t exist anymore. For example, the username input doesnā€™t exist for the ā€œupdate passwordā€ version of the layout.

Looks like this :

add_edit_user_form.py

def layout(action="add", **kwargs):
	"""
	Same layout function for Add User / Edit User / Update Password with a minor 
	change depending on the action input
	args:
		action: "add", "edit", "update" (str)
	"""
	action = action.lower()
	index = 0


	rows = list()

	if action in ["add", "edit"]:
		# Username + email form row
		rows.append(username_email_row(index))
		# Role + Accessible sites form row
		rows.append(role_sites_row(index, role, sites))
	if action in ["add", "update"]:
		# Create / Update password + Confirm form row
		rows.append(password_row(index))

	return dbc.Card([
		dbc.CardBody([
			html.H1("H1"),
			html.Br(),
			dbc.Form([
				dbc.Row(dbc.Col(rows)),
				dbc.Button(btn_label, id={"type": f"{action}-user-form-btn", "index": index}),
				dbc.Alert(
			            "",
			            id="add-edit-user-alert",
			            is_open=False,
			            duration=4000,
			            dismissable=True
		        	),
			])
		])
	])


# And the callback associated 
@app.callback(
	[
		Output("add-edit-user-alert", 'is_open'),
		Output("add-edit-user-alert", 'children'),
		Output("add-edit-user-alert", 'color'),
		Output("single-user-action-store", "data")
	],
	[
		Input({"type": "add-user-form-btn", "index": ALL }, 'n_clicks'),
		Input({"type": "edit-user-form-btn", "index": ALL }, 'n_clicks'),
		Input({"type": "update-user-form-btn", "index": ALL }, 'n_clicks')

	],
	[
		State({"type": "add-edit-username-input", "index": ALL}, "value"),
		State({"type": "add-edit-email-input", "index": ALL}, "value"),
		State({"type": "add-edit-role-input", "index": ALL}, "value"),
		State({"type": "add-edit-sites-input", "index": ALL}, "value"),
		State({"type": "add-edit-password-input", "index": ALL}, "value"),
		State({"type": "add-edit-conf-password-input", "index": ALL}, "value"),
	])
def add_edit_update_user(add_n_clicks, edit_n_clicks, update_n_clicks, username, email, role, sites, password):

        # some more logic 
        # the only annoying part is since we need to match on index ALL 
        # (otherwise we would need to use the pattern MATCH on every input, output, state. 
        # At least using MATCH generated a bug which makes sense as we can create several callbacks for the same output ).
        # Because of this, inputs of the functions are array-like objects. I just do this to get them back as I know I always have 1-length arrays
        
        [add_n_clicks] = add_n_clicks

       # more logic and return

Thank you @alexcjohnson. The To Do example was very helpful.
One more question, is there any limitation on handling of exception for missing id in callbacks?

I ran into a problem with missing input and output callbacks and the resulting exception was not ignored.
Here is an example, where ā€œavailable-periodsā€ id exists but the other idā€™s did not. If I comment out any of the callbacks the app will at least run. However in the current state, the app will only say ā€œError loading dependenciesā€.


@app.callback(
    Output('available-periods', 'children'),
    [Input({'type': 'period-name-textbox', 'index': ALL}, 'value')]
)
def update_available_periods(values):
    return []

@app.callback(
    [Output({'type': 'filter-period-dropdown', 'index': ALL}, 'options')],
    [Input('available-periods', 'children')],
)
def update_selection_periods(values):
    return []

@jaser I think youā€™ve uncovered a bug :tada: - looks like something breaks when an ALL pattern is the only output but it doesnā€™t match any components. Weā€™ll get that fixed in the next release!

1 Like

Thanks for confirming this @alexcjohnson. I also tried to create a dummy component and use it in the output along the ALL pattern output but that did not work either.
Looking forward to future releasesā€¦

@alexcjohnson,

I notice that ctx.inputs_list is a bit greedy with what it captures. For instance, I have a callback below in which I want to capture just the value prop in one list, and the relayoutData in the other, but instead, I get all components and have to filter out the ones that donā€™t apply.

The filtering stage only adds two extra lines/per component, but it is rather inefficient as it requires me to iterate through every pattern matched component. Would it be possible to have this already filtered out before being put into the inputs_list?

Do you see any value add in having it unfiltered to begin with?

@app.callback(
    Output('state', 'data'),
    [
        Input({'module': ALL, 'id': ALL}, 'value'),
        Input({'module': ALL, 'submodule': ALL, 'id': ALL}, 'value'),
        Input({'module': ALL, 'submodule': ALL, 'id': ALL}, 'relayoutData'),
    ],
    [State('state', 'data')],
)
def update_state(*args):
    
    module_values, submodule_values, graph_relayouts = ctx.inputs_list

    module_values = [i for i in module_values if 'value' in i]
    if module_values:
        for value in module_values:
            handle_value(value, prev)

    submodule_values = [i for i in submodule_values if 'value' in i]
    if submodule_values:
        for value in submodule_values:
            handle_value(value, prev)

    graph_relayouts = [i for i in graph_relayouts if 'value' in i]
    if graph_relayouts:
        for relayout in graph_relayouts:
            handle_relayout(relayout, prev)


1 Like

When I upgraded to dash 1.11, my dcc.Interval stopped working. I used to dynamically set max_intervals through a button and then the interval would fire every interval Iā€™ve setup in the layout. It worked fine until dash 1.10. Now it doesnā€™t anymore. Did anything change under the hood? I couldnā€™t find anyone mentioning the same issueā€¦

Thanks everyone here who helped find edge cases and regressions in Dash 1.11. Dash 1.12 was released this afternoon with fixes for these issues, in addition to a bunch of new features - check it out! šŸ“£ Dash v1.12.0 Release - Pattern-Matching Callbacks Fixes, Shape-drawing, new DataTable conditional formatting options, and more

@mbkupfer I didnā€™t get a chance to look at your callback_context.inputs_list issue yet but I have not forgotten about it.

@MM-Lehmann Lots of things changed under the hood :slight_smile: I encourage you to try again with Dash 1.12. If the problem persists start a new topic here - feel free to @ mention me and Iā€™ll take a look but weā€™ll need a reproducible code snippet in order to help.

3 Likes

Thanks @alexcjohnson. Really appreciate the transparent communication and quick update. Canā€™t wait to try out 1.12!

2 Likes

Got it nailed down. I had quite a few circular dependencies in my app which didnā€™t pose a problem until dash 1.10. Somehow they now seem to interfere with threaded intervals (running alongside a long running callback). Actually they are blocking the entire callback chain of the circular depsā€¦
I wish there was an easy way of getting intermediate feedback from a long running callback. Whatever the change was, it became even more difficult now.

edit: found a more robust way now, avoiding circular deps. So this is a non-issue, I supposeā€¦

1 Like

Seeing the same console errors on my app. Found this thread on the React forum:

Seems like React got more picky recently and lots of people are bumping into this.

I think the verdict was that the issue was always present but the warnings only became apparent now. Nobody really can say if there is an impact at all.

Hi Alex, I got the above warning using dash 1.13.0. Is it still an unsolved problem? Thanks!

Hi Dash community,
In case anyone is interested, I was very excited when I learned to use pattern-matching callbacks, so I wanted to share what I know with others.

I created a tutorial that explains dynamic callbacks. I hope it helps others. This is really a Dash game-changer. Thank you to the Dash Plotly team that developed this.

5 Likes

@Lion Iā€™m not sure which warning youā€™re referring to - anyway this thread is getting long and hard to follow, maybe you can start a new thread with more specifics of what problem youā€™re seeing?

Iā€™m having performance trouble with this as well. I have a map with thousands of markers and a callback that is triggered when a marker is clicked. It can take a minute just to send all the thousands of components to the callback, and then once the function within the callback finally starts executing to filter out and get just what I need it only takes a couple seconds. Iā€™ll be following the convo in case any updates to this may be on the way. :grinning:

Would it be OK, if the markers were clustered, or do you need all markers shown at the same time?

you might want to make it clientside (also in my case it is even slower on simple stuff like modifing a input string), but still faster than up and donw the server.

i came here for the warnings: dash 1.13.4

react-dom@16.v1_5_1m1595245269.13.0.js:82 Warning: Cannot update a component from inside the function body of a different component.
    in n (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 u)

Should i be concerned will it be fixed?

Hi lovely people! Iā€™d like to share with you a misterious problem, I donā€™t know what is exactly happening. One of my callbacks is triggered even when the input buttons are not pressed. These buttons are dinamically generated following a pattern, and in the input I use MATCH to fire the callback.

The first two clicks were ok, but then suddenly -even if I donā€™t click the button- the callback is fired.

I included dash.callback_context.triggered in order to get what was happening, appending it to a list every time the callback was triggered, and I got that the last item is not exactly my button: ā€œFalsyList object of dash._callback_context moduleā€. Whatā€™s happening here?

I appreciate your help. Thanks

Note that prop_id is a dot.Captura

Hi, when dealing with Pattern Matching Clawbacks, does there exist any elegant solution to my question
https://community.plotly.com/t/how-to-save-and-load-dash-callback-context-inputs-inputs-list/49682?u=mike12

Thanks a lot

A post was split to a new topic: Having trouble with a pattern matching callback example