PreventUpdate for ClientSide callbacks

I’m trying to write a ClientSide callback that can throttle how often a ServerSide callback gets called by some custom logic. If this was a ServerSide callback, I would raise dash.exceptions.PreventUpdate, but that isn’t available in the ClientSide callback. I can throw an error, but this clutters the console. Is there a recommended way to do this?

Edit: I will note that debounce on a ClientSide callbacks doesn’t work, because while the debounce will reduce how often data gets sent back to the server, it still effectively returns undefined, which is considered an update and triggers the next callback.

1 Like

See 📣 Dash 0.41.0 released regarding no_update in the Partial Multi-output section

no_update is on the python side still. I’m wondering what I can use on the javascript side to be in effect no_update. For now, I continue to just throw Exceptions.

Raising an exception / throwing an error is the way to go right now. no_update nor callback_context has been provided for the ClientsideCallbacks yet.

Hi chriddyp,

Thanks for confirming my suspicions. I’m still struggling to properly throttle the calls back to the server. Mostly because we can’t make ClientSide callbacks that don’t return anything, IE they mutate the DOM. There might be a way to do this with a Promise, but those don’t work as well for ClietnSide callbacks right now. I imagine directly mutating the DOM is a no-no with React, but I’m not seeing much of an alternative. My thoughts at a work around are this:
1.) Make a dummy Store
2.) Create a ClientSide callback that modifies a useless parameter for the Store such as it’s class, and have that return always be the same thing: ""
This is an attempt to make a ClientSide callback that basically has no Output
3.) Run a debounced function that mutates the data parameter of the dummy Store
4.) Have a ServerSide callback using that dummy Store’s data as an Input.

It’s also very possible I’m overthinking this.

Hi chirddyp,

Is it still the case that callback_context is not available in clientside callbacks? If so, do you have any suggestions for an effective workaround for this clientside? I’m using a store component as one of the inputs and need to know when the data in the store has changed, otherwise, follow some other logic.

I’m not sure if this helps, but maybe you could try something like this:

app.clientside_callback(
"""
function (input1,store1) {
  if (input_is_not_what_youre_looking_for(input1)) {
    return store1;
  }
  return process_input(input1);
}
"""
Output("output","data"),
[Input("input","data")],
[State("output","data")])

In this case, if you don’t want to update (as determined by the function input_is_not_what_youre_looking_for), then you just return the old contents of the output (as passed in through the state inputs). But if it is what you’re looking for, it returns some new value. input_is_not_what_youre_looking_for could simply check if that input is undefined. Note that you can include additional javascript functions by putting them in a file ending with .js in the assets/ folder.

I had a similar problem these days and solved it as shown below.

The value of a slider is talken as the input to a throttle routine which outputs to a hidden <div id='slider-output-throttle'> only if some time has passed, otherwise I send no_update, which is meanwhile defined for clientside callbacks as well.
The time of the last update is stored in the clientside namespace of the output.
The server side callback is then attached to the throttled output, which returns the output and redraws the figure.

import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

app = dash.Dash(__name__, suppress_callback_exceptions = True)

app.layout = html.Div([
    dcc.Slider(
        id='my-slider',
        min=0,
        max=20,
        step=0.01,
        value=10,
        updatemode='drag'
    ),
    html.Div(id='slider-output-throttle', hidden = True),
    html.Div(id='slider-output-backend'),
    dcc.Graph(id='my-graph')
])

app.clientside_callback(
    """
        function throttle(value) {
            let t_throttle = 150;
            // ns refers to the clientside namespace
            ns.t0 = ns.t0 || 0;
            let dt = Date.now() - ns.t0;
            if ( dt < t_throttle) {
                return window.dash_clientside.no_update
            } else {
               ns.t0 = Date.now();
               return value
            }
        }
    """,
    Output('slider-output-throttle', 'children'),
    [Input('my-slider', 'value')])

@app.callback(
    Output('slider-output-backend', 'children'),
    [Input('slider-output-throttle', 'children')])
def update_output(value):
    return 'Server says "{}"'.format(value)

@app.callback(Output("my-graph", "figure"), [Input("slider-output-throttle", "children")])
def update_output(value):
        figure = {
            'data': [
                {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
                {'x': [1, 2, 3], 'y': [2, value, 5], 'type': 'bar', 'name': u'Montréal'},
            ],
            'layout': { 'title': 'Dash Data Visualization' }
        };
        return figure

if __name__ == "__main__":
    app.run_server(port=8070)

The same is also possible from Julia! Although clientside callbacks are not yet documented ( :wink: ) they work almost as in Python, except that always the same routine callback!() is used. Decision between serverside and clientside is done by the magic of multiple dispatch.

using Dash
using DashHtmlComponents
using DashCoreComponents

app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"])

app.layout = html_div() do
    dcc_graph(id = "my-graph", style = (width = "600px",) ),
    html_div(dcc_slider(
        id = "my-slider",
        min = 0,
        max = 20,
        step = 0.1,
        value = 10,
        updatemode = "drag"
    ), style = (width = "600px",)),
    html_div(id = "my-slider-throttle", hidden = true),
    html_div(id = "my-slider-backend")
end

# clientside callback, if a function is passed as a (javascript) string
callback!("""
    function throttle(value) {
        let t_throttle = 150;
        // ns refers to the clientside namespace
        ns.t0 = ns.t0 || 0;
        let dt = Date.now() - ns.t0;
        if ( dt < t_throttle) {
            return window.dash_clientside.no_update
        } else {
           ns.t0 = Date.now();
           return value
        }
    }
""", app, Output("my-slider-throttle","children"), Input("my-slider", "value"))

# serverside callback, if a native julia function is passed
# (here by a pure function with `do` block syntax)
callback!(app, [Output("my-slider-backend", "children"), Output("my-graph", "figure")], Input("my-slider-throttle", "children")) do input_value
    figure = (
        data=[
            (x = [1, 2, 3], y = [4, 1, 2], type = "bar", name = "SF"),
            (x = [1, 2, 3], y = [2, input_value, 5], type = "bar", name = "Montréal")
        ],
        layout = (title = "Dash Data Visualization",)
    )
    return input_value, figure
end

run_server(app, "0.0.0.0", 8080)
1 Like

This was recently added in Dash 1.13.0 :tada:

Will there be an official debounce/throttle mechanism in the future? My hack works nicely, but it’s still a hack…

Callback-level throttling is definitely a great idea. It’s not on the definite roadmap right know though. That being said, we would accept a PR that would add it or would build it more near term out if an org sponsored the work (https://plotly.com/products/consulting-and-oem)

Could you give a hint where the throttling should take place? I’m not at all familiar with the dash architecture and callbacks are organised somewhat differently to what I’m used to.
I found a commit with a hint on throttling, but that file disappeared so I did not dig further.

Sure! I’ll make a GitHub issue for better visibility :slight_smile:

Here is some context: https://github.com/plotly/dash/issues/1311

Welcome to Dash for Julia by the way :smiley_cat: :raised_hands:

I am so glad this is available - works perfectly!