Execution of callbacks writting to a single dcc.Store

Hello,

I have an app where multiple controls (textInputs, dropdowns, etc) write information into a dcc.Store (addressMemory in the following example) to keep track of all the values. This works perfect when user introduces the data as he/she operates with one control at a time.

My problem arises when I try to upload a predefined configuration automatically. This executes a callback that updates all the control’s values at once. This executes individually every control’s callback but the dcc.Store finally gets only the value of the last control to execute its callback.

The example show what I try to expose, when executing the function bulkSetting, both callbacks (change_spiMode and change_crcMode) are executed but the dcc.Store addressMemory will endup with only one of the changes…

@callback(
    Output("addressMemory", "data", allow_duplicate=True),
    [
        Input("spiMode", "value"),
    ],
    State("addressMemory", "data"),
    prevent_initial_call=True,
)
def change_spiMode(spiMode,addressMemory):
    if spiMode:
        addressMemory["0x00"]["custom"]="0x{:02x}".format(int(spiMode,2))
    else:
        addressMemory["0x00"]["custom"]=addressMemory["0x00"]["default"]
        return no_update
    return addressMemory

@callback(
    Output("addressMemory", "data", allow_duplicate=True),
    [
        Input("crcMode", "value"),
    ],
    State("addressMemory", "data"),
    prevent_initial_call=True,
)
def change_crcMode(crcMode,addressMemory):
    if crcMode:
        addressMemory["0x10"]["custom"]="0x{:02x}".format(int(crcMode,2))
    else:
        addressMemory["0x10"]["custom"]=addressMemory["0x10"]["default"]
        return no_update
    return addressMemory

def bulkSetting():
    set_props(spiMode,{'value':"{0:08b}".format(int(value,16))})
    set_props(crcMode,{'value':"{0:08b}".format(int(value,16))})

I wonder if there is any way to perform the callback execution sequentially so that the dcc.Store for the second callback is the one outputted by the first and so on…

Thanks a lot in advance!!

Hi @mizamae

You have many solutions.

  1. run callbacks sequentially: you just need to pass callback A’s output into callback B’s inputs. In this case, it means having addressMemory as an Input and not only a State. You could set this on both callbacks.
    But in this case, you’ll need to add a condition to ensure that the callbacks are not running in an infinite circular way, by checking that “crcMode” or “spiMode” was part of the input triggering the callback. Take a look into ctx.triggered (Advanced Callbacks | Dash for Python Documentation | Plotly)

  2. Merge the two callbacks : you’ll end up removing “allow_duplicate” and having both crcMode and spiMode as inputs. Then, you just need to check which input was fired using ctx.triggered again. Possible values will be crcMode, spiMode, or both.

@callback(
    Output("addressMemory", "data"),
    [
        Input("spiMode", "value"),
        Input("crcMode", "value"),
    ],
    State("addressMemory", "data"),
    prevent_initial_call=True,
)
def change_spicrcMode(spiMode, crcMode, addressMemory):

    spi_triggered = "spiMode" in [x["prop_id"] for x in ctx.triggered]
    crc_triggered = "crcMode" in [x["prop_id"] for x in ctx.triggered]

    if spi_triggered:
        if spiMode:
            addressMemory["0x00"]["custom"]="0x{:02x}".format(int(spiMode,2))
        else:
            addressMemory["0x00"]["custom"]=addressMemory["0x00"]["default"]
            return no_update

    if crc_triggered: 
        if crcMode:
            addressMemory["0x10"]["custom"]="0x{:02x}".format(int(crcMode,2))
        else:
            addressMemory["0x10"]["custom"]=addressMemory["0x10"]["default"]
            return no_update

    return addressMemory

(not tested, btw the return no_update might be removed or changed)

  1. use two other dcc.Store: addressMemoryCrc, addressMemorySpi ; and create a callback that will merge both of them inside addressMemory. That way you’ll also get rid of the allow_duplicate option.

In my opinion the second option is better :slight_smile: . Solution 3 is also good.
allow_duplicate is useful but also lead to “callback bad design”, so I would recommend using it only at last resort.
For 99% of the cases it’s preferable to have an output updated by only one callback.

I hope it helps!!

2 Likes

Hi @mizamae

So actually your problem inspired me to write a full explanation on my blog. Because I had this problem as well before, and I think it’s an interesting problem to highlight callback design patterns.

I posted here:

An additional solution for you is to use Patch(), I didn’t think about it at first.