Clientside callbabks - best practices

I have developed a Dash application that I’m generally pleased with. At this point, I am looking to optimize its performance. While the documentation advises against using client-side callbacks with database queries, I am curious about their most effective application scenarios.

For instance, I have several features, such as modal toggles, dynamic column definition updates in AG-Grid, and dcc.Store updates, that are currently managed through server-side callbacks.

Here’s how I’m implementing these features:

  1. Modal Openings:
@callback(
    Output("p-modal", "is_open"),
    Input("p-modal-button", "n_clicks"),
    State("p-modal", "is_open"),
)
def toggle_modal_pp(n1, is_open):
    if n1:
        return not is_open
    return is_open
  1. Updating Column Definitions in AG-Grid:
@callback(
    Output("m_data_table", "columnDefs"),
    Output("m_data_table", "columnSize"),
    Input("m-toggle", "value"),
    prevent_initial_call=True,
)
def toggle_cols(value):
    if 2 in value:
        return columns_methodCDefs_wide, "autoSize"
    return columns_methodCDefs_narrow, "SizeToFit"
  1. Updating dcc.Store, with two examples:
@callback(
    Output('dropdown-store', 'data'),
    Input('dropdown', 'value'),
    prevent_initial_call=True
)
def on_api_choice(selected_value):
    if not selected_value:
        raise PreventUpdate
    return selected_value

@callback(
    Output('current-url', 'data'),
    Input('dropdown1', 'value'),
    Input('dropdown1', 'value'),
    prevent_initial_call=True,
)
def update_upper_filters(dd1, dd2):
     if dd1 is None:
        return None 
    params = [
        ('dd1', quote(concept) if dd1 else []),
        *((('dd2', quote(v)) for v in dd2) if strength else []),
    ]
    
    params = [item for item in params if item is not None and item[1] is not None]

    dds = "&".join(f"{k}={v}" for k, v in params)
    return dds

I’m interested in understanding when it’s most appropriate to migrate these to client-side callbacks. Are there specific conditions or types of interactions that are better suited for client-side logic? Also, are there performance benchmarks or case studies illustrating the impact of using client-side callbacks for similar operations?

Hi @davzup89

The best way to improve your app is to find out where the bottlenecks are. You can check the dev tools to see how long those callbacks are taking, or better yet, use something like this Werzeug profiler:

2 Likes

Hello @davzup89,

Here is a simple exercise in the comparison between clientside callbacks vs generic callbacks:

from dash import html, Dash, dcc, Input, Output, State
import dash_mantine_components as dmc
import time

app = Dash(__name__)

app.layout = html.Div([
    dmc.NumberInput(id='networkDelay', label='Network delay in seconds', step=0.1, precision=2, max=0.9, min=0, value=0),
                       dcc.Interval(id='timer', interval=1000),
                       dmc.NumberInput(id='clientside', label='Client Side Response', disabled=True, value=0),
                       dmc.NumberInput(id='severside', label='Server Side Response', disabled=True, value=0)
                       ])

app.clientside_callback(
    """function (n, v) {
        if (n) {
            v = parseInt(v || 0)+1
            return v
        }
    }""",
    Output('clientside', 'value'),
    Input('timer', 'n_intervals'),
    State('clientside', 'value'),
)

@app.callback(
    Output('severside', 'value'),
    Input('timer', 'n_intervals'),
    State('severside', 'value'),
    State('networkDelay', 'value')
)
def serverSide(n, v, delay):
    if n:
        time.sleep(delay or 0)
        return int(v)+1
    return 0


app.run(debug=True)

At 0 we notice that there is a latency of 65ms (less than a 10th of a second), not a big deal:


Increasing the network latency we notice this gap starts getting wider (the counts on the left start getting messed up because of not being able to render the adjustment):


This also assumes that the server can handle your request immediately and will not have to wait for the server to respond. (1 user with 1 callback being triggered)

If, however, you have multiple users, you’ll begin to go into a queue line and get a response accordingly. Thus a simple thing like opening a modal or increasing the count like this example will take much longer than expected.


My main thinking is, if it is something simple enough where you can make it a clientside callback, you really should in order to decrease the number of calls to the server, this will let your server respond quicker to things that are actually critical. :slight_smile:

4 Likes

If the callback is manipulating only data that are already present clientside, I would consider it a candidate for conversion to a clientside callback.

If the logic is very simple, I would typically just do it right away; otherwise I tend to start out with a normal callback and only move to clientside, if I notice performance issues :blush:

4 Likes