Dash circular callback

Hi all,

I am trying to implement a circular callback that would recursively process elements in a list (stored in a Store component).

For some reason the callback is only fired once. Am I doing something wrong? Is it possible to achieve what I have in mind?

from dash import Dash, dcc, callback, Input, Output, State

app = Dash(__name__)

app.layout = [
    dcc.Store(
        id='store',
        data=[1, 2, 3]
    ),
    dcc.Store(
        id='even-store',
        data=[]
    ),
    dcc.Store(
        id='odd-store',
        data=[]
    )
]

@callback(
    Output('store', 'data'),
    Output('even-store', 'data'),
    Output('odd-store', 'data'),
    Input('store', 'data'),
    State('even-store', 'data'),
    State('odd-store', 'data')
)
def classify(remaining, evens, odds):
    if remaining:
        n = remaining[0]
        if n % 2 == 0:
            print(f"even: {len(remaining)}")
            return remaining[1:], evens + [n], odds
        else:
            print(f"odd: {len(remaining)}")
            return remaining[1:], evens, odds + [n]
    else:
        print(f"nothing")
        return []

debug = True

if __name__ == '__main__':
    app.run(debug=debug, use_reloader=debug)

Output:

 * Serving Flask app 'test_app'
 * Debug mode: on
odd: 3

Expected output:

 * Serving Flask app 'test_app'
 * Debug mode: on
odd: 3
even: 2
odd: 1
nothing

Hello @Jess,

Welcome to the community!

I don’t think you can do this, since the callback is updating itself it’s not going to work.

You could update a button or some other component.

Actually, remove the output to itself, and instead use set_props, to itself. This will bypass the check for the circular output.

I was trying to follow the example in Advanced Callbacks | Dash for Python Documentation | Plotly where Input and Output are effectively the same.

I have never tried set_props (or clientside_callback for that matter). Will have a look now.

Thanks!

For your example, I’d do this:

from dash import Dash, dcc, callback, Input, Output, State

app = Dash(__name__)

app.layout = [
    dcc.Store(
        id='store',
        data=[1, 2, 3]
    ),
    dcc.Store(
        id='even-store',
        data=[]
    ),
    dcc.Store(
        id='odd-store',
        data=[]
    )
]

@callback(
    Output('even-store', 'data'),
    Output('odd-store', 'data'),
    Input('store', 'data'),
    State('even-store', 'data'),
    State('odd-store', 'data')
)
def classify(remaining, evens, odds):
    if remaining:
        n = remaining[0]
        dash.set_props('store': {'data': remaining[1:]})
        if n % 2 == 0:
            print(f"even: {len(remaining)}")
            
            return evens + [n], odds
        else:
            print(f"odd: {len(remaining)}")
            return evens, odds + [n]

    print(f"nothing")
    return [], []

debug = True

if __name__ == '__main__':
    app.run(debug=debug, use_reloader=debug)
1 Like

Thanks, it looks like this works. I wasn’t aware set_props was available directly from dash.

FYI, you have a typo in

dash.set_props('store': {'data': remaining[1:]}

It should be

dash.set_props('store', {'data': remaining[1:]}

Not sure if you want to edit or if I should accept the solution as is?

Updated.

And yes, set_props is available in python and the clientside. The caveat is that its not subject to Dash restraints. :slight_smile:

Thanks, I just came back to Python Dash and I had missed this. If I may ask, the .set_props looks pretty much like what a regular callback with an Output does, so why don’t we use .set_props everywhere? I’m guessing there is a good reason, .set_props just looks like cheating.

Ah never mind, just read https://dash.plotly.com/advanced-callbacks#setting-properties-directly and it’s very clear already. Thanks for your support!

2 Likes