dcc.Store as Input not triggering callback

Hello,

As the title say, I am using some dcc.Store as input in some callback, but it is not getting triggered.

My code is basically the next:


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


app = Dash(__name__)

app.layout = html.Div([
    dcc.Store('ServerTriggerer', storage_type='session', data={'Value': None}),
    dcc.Store('ClientTriggerer', storage_type='session', data={'Value': None}),
    html.Div(
        children=[
            dcc.Dropdown(
                options=['A', 'B', 'C'],
                **{'id': 'Select_That_Triggers'}  # I know I dont need the dict unpack, but id is a reserved function in python, so it bothers me to use it as key.
            ),
            dcc.Dropdown(
                options=[],
                **{'id': 'Select_That_Receives_Values'}
            )
        ]
    )
])


server_trigger_js = """
function trigger_server(val, server_triggerer_data, client_triggerer_data){
    console.log('Server triggerer called', val);
    if (val === null){
        client_triggerer_data['Value'] = val
        return dash_clientside.no_update, client_triggerer_data;
    }
    server_triggerer_data['Value'] = val;
    return server_triggerer_data, dash_clientside.no_update;
}
"""

client_update_dropdown_js = """
function update_dropdown(client_triggerer_data){
    console.log('Update Dropdown called');
    const options = client_triggerer_data['Value'];
    return options;
}
"""


clientside_callback(
    server_trigger_js,
    Output('ServerTriggerer', 'data'),
    Output('ClientTriggerer', 'data', allow_duplicate=True),
    Input('Select_That_Triggers', 'value'),
    State('ServerTriggerer', 'data'),
    State('ClientTriggerer', 'data'),
    prevent_initial_call=True
)


@callback(
    Output('ClientTriggerer', 'data'),
    Input('ServerTriggerer', 'data'),
    State('ClientTriggerer', 'data'),
    prevent_initial_call=True
)
def get_options_server(server_triggerer_data, client_triggerer_data):
    print('server called')
    OPTIONS_MAP = {'A': [1,2,3], 'B': [4,5,6], 'C': [7,8,9]}
    selected_value = server_triggerer_data['Value']
    options = OPTIONS_MAP[selected_value]
    client_triggerer_data['Value'] = options
    return client_triggerer_data


clientside_callback(
    client_update_dropdown_js,
    Output('Select_That_Receives_Values', 'options'),
    Input('ClientTriggerer', 'data'),
    prevent_initial_call=True
)

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

To simplify the understanding the flow is the next:

Input_A -- client: Stores Value --> Store_A -- server: gets_new_value_and_stores --> Store_B -- client: add_options_to --> Input_B

In case some condition is met:

Input_A -- client: Stores Value --> Store_B -- client: add_options_to --> Input_B

What happens now:

  • First client function is called.
  • First store is updated.
  • Nothing else happens from here.

Thanks in advance.

Hello @VanceVisaris,

You are experiencing a variable updating in place. This keeps the callback from triggering.

If you want to use it the way you are doing it, you can either create a new variable or use a clientside patch, similar to the python patch.

1 Like

Something like this?

server_trigger_js = """
function trigger_server(val, server_triggerer_data, client_triggerer_data){
    console.log('Server triggerer called', val);

    const patch_server = new dash_clientside.Patch;
    const patch_client = new dash_clientside.Patch;
    console.log(patch_server, patch_client);
    if (val === null){
        client_triggerer_data['Value'] = val
        return dash_clientside.no_update, patch_client.assign([], client_triggerer_data).build();
    }
    server_triggerer_data['Value'] = val;
    return patch_server.assign([], server_triggerer_data).build(), dash_clientside.no_update;
}
"""

It is not working either.

No, you are still updating the value in place:

server_trigger_js = """
function trigger_server(val){
    console.log('Server triggerer called', val);
    var patch = new dash_clientside.Patch;
    const _patch = patch.assign(['Value'], val).build()
    if (val === null) {
        return [dash_clientside.no_update, _patch];
    }
    return [_patch, dash_clientside.no_update];
}
"""

clientside_callback(
    server_trigger_js,
    Output('ServerTriggerer', 'data'),
    Output('ClientTriggerer', 'data', allow_duplicate=True),
    Input('Select_That_Triggers', 'value'),
    #State('ServerTriggerer', 'data'), -- no longer needed
    #State('ClientTriggerer', 'data'),  -- no longer needed
    prevent_initial_call=True
)

However, the thing to note here was that the clientside needed to be wrapped in [] to make it an array. Its one of the clientside callback nuisances. Your original function did work, but only because its a “simple” object. The patch way will work with even more complex structures, like a dcc.Graph.

3 Likes