To get the most out of this page, make sure you’ve read about Basic Callbacks in the Dash Fundamentals.
Sometimes callbacks can incur a significant overhead, especially when they:
- receive and/or return very large quantities of data (transfer time)
- are called very often (network latency, queuing, handshake)
- are part of a callback chain that requires multiple roundtrips between the browser and
When the overhead cost of a callback becomes too great and no other optimization is possible, the callback can be modified to be run
directly in the browser instead of a making a request to .
The syntax for the callback is almost exactly the same; you use
Input
and Output
as you normally would when declaring a callback,
but you also define a JavaScript function as the first argument to the
callback!()
function.
For example, the following callback:
callback!(
app,
Output("out-component", "value"),
Input("in-component1", "value"),
Input("in-component2", "value")
) do large_value1, large_value2
large_value_output = some_transform(large_value1, large_value2)
end
Can be rewritten to use JavaScript like so:
callback!(
"""
function(largeValue1, largeValue2) {
return someTransform(largeValue1, largeValue2);
}
"""
app,
Output("out-component", "value"),
Input("in-component1", "value"),
Input("in-component2", "value")
)
You also have the option of defining the function in a .js
file in
your assets/
folder. To achieve the same result as the code above,
the contents of the .js
file would look like this:
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
large_params_function: function(largeValue1, largeValue2) {
return someTransform(largeValue1, largeValue2);
}
}
});
In , the callback would now be written as:
callback!(
ClientsideFunction("clientside", "large_params_function"),
app,
Output("out-component", "value"),
Input("in-component1", "value"),
Input("in-component2", "value")
)
Below of using clientside callbacks to update a
graph in conjunction with a component. In , we update a
component on the backend; to create and display the graph, we have a clientside callback in the
frontend that adds some extra information about the layout that we
specify using the radio buttons under “Graph scale”.
Note that, in this example, we are manually creating the figure
dictionary by extracting the relevant data from the
DataFrame. This is what gets stored in our
dcc_store
component;
expand the “Contents of figure storage” above to see exactly what
is used to construct the graph.
PlotlyJS.jl enables you to create one-line declarations of figures. We can rework the example above to use Plotly Express.
using Dash
using HTTP, PlotlyJS, CSV, DataFrames, JSON
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
app = dash(external_stylesheets=external_stylesheets)
read_remote_csv(url) = DataFrame(CSV.File(HTTP.get(url).body))
df = read_remote_csv("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")
available_countries = unique(df.country)
app.layout = html_div() do
dcc_graph(id="clientside-graph-plotlyjs"),
dcc_store(
id="clientside-figure-store-plotlyjs",
),
"Indicator",
dcc_dropdown(
id="clientside-graph-indicator-plotlyjs",
options=[
(label = "Population", value = "pop"),
(label = "Life Expectancy", value = "lifeExp"),
(label = "GDP per Capita", value = "gdpPercap"),
],
value="pop",
),
"Country",
dcc_dropdown(
id="clientside-graph-country-plotlyjs",
options=[
(label = country, value = country) for country in available_countries
],
value="Canada",
),
"Graph scale",
dcc_radioitems(
id="clientside-graph-scale-plotlyjs",
options=[(label = x, value = x) for x in ["linear", "log"]],
value="linear",
),
html_hr(),
html_details([
html_summary("Contents of figure storage"),
dcc_markdown(id="clientside-figure-json-plotlyjs"),
])
end
callback!(
app,
Output("clientside-figure-store-plotlyjs", "data"),
Input("clientside-graph-indicator-plotlyjs", "value"),
Input("clientside-graph-country-plotlyjs", "value"),
) do indicator, country
plot(df[df.country .== country, :], x=:year, y=Symbol(indicator), mode="markers")
end
callback!(
"""
function(figure, scale) {
if(figure === undefined) {
return {'data': [], 'layout': {}};
}
const fig = Object.assign({}, figure, {
'layout': {
...figure.layout,
'yaxis': {
...figure.layout.yaxis, type: scale
}
}
});
return fig;
}
""",
app,
Output("clientside-graph-plotlyjs", "figure"),
Input("clientside-figure-store-plotlyjs", "data"),
Input("clientside-graph-scale-plotlyjs", "value"),
)
callback!(
app,
Output("clientside-figure-json-plotlyjs", "children"),
Input("clientside-figure-store-plotlyjs", "data"),
) do data
buf = IOBuffer()
JSON.print(buf, data, 2)
js = String(take!(buf))
"""
```json
$(js)
```
"""
end
run_server(app, "0.0.0.0", debug=true)
None
Again, you can expand the “Contents of figure storage” section
above to see what gets generated. You may notice that this is
quite a bit more extensive than the previous example; in
particular, a layout
is already defined. So, instead of creating
a layout
as we did previously, we have to mutate the existing
layout in our JavaScript code.
Dash 2.4 and later supports clientside callbacks that return promises.
In this example, we fetch data (based on the value of the dropdown) using an async clientside callback function that outputs it to a dash_table.DataTable
component.
This example has not been ported to Julia yet - showing the Python version instead.
Visit the old docs site for Julia at: https://community.plotly.com/c/dash/julia/20
from dash import Dash, dcc, html, Input, Output, dash_table, clientside_callback
app = Dash()
app.layout = html.Div(
[
dcc.Dropdown(
options=[
{
"label": "Car-sharing data",
"value": "https://raw.githubusercontent.com/plotly/datasets/master/carshare_data.json",
},
{
"label": "Iris data",
"value": "https://raw.githubusercontent.com/plotly/datasets/master/iris_data.json",
},
],
value="https://raw.githubusercontent.com/plotly/datasets/master/iris_data.json",
id="data-select",
),
html.Br(),
dash_table.DataTable(id="my-table-promises", page_size=10),
]
)
clientside_callback(
"""
async function(value) {
const response = await fetch(value);
const data = await response.json();
return data;
}
""",
Output("my-table-promises", "data"),
Input("data-select", "value"),
)
if __name__ == "__main__":
app.run(debug=True)
This example uses promises and sends desktop notifications to the user once they grant permission and select the Notify button:
This example has not been ported to Julia yet - showing the Python version instead.
Visit the old docs site for Julia at: https://community.plotly.com/c/dash/julia/20
from dash import Dash, dcc, html, Input, Output, clientside_callback
app = Dash()
app.layout = html.Div(
[
dcc.Store(id="notification-permission"),
html.Button("Notify", id="notify-btn"),
html.Div(id="notification-output"),
]
)
clientside_callback(
"""
function() {
return navigator.permissions.query({name:'notifications'})
}
""",
Output("notification-permission", "data"),
Input("notify-btn", "n_clicks"),
prevent_initial_call=True,
)
clientside_callback(
"""
function(result) {
if (result.state == 'granted') {
new Notification("Dash notification", { body: "Notification already granted!"});
return null;
} else if (result.state == 'prompt') {
return new Promise((resolve, reject) => {
Notification.requestPermission().then(res => {
if (res == 'granted') {
new Notification("Dash notification", { body: "Notification granted!"});
resolve();
} else {
reject(`Permission not granted: ${res}`)
}
})
});
} else {
return result.state;
}
}
""",
Output("notification-output", "children"),
Input("notification-permission", "data"),
prevent_initial_call=True,
)
if __name__ == "__main__":
app.run(debug=True)
You can use dash_clientside.callback_context.triggered_id
within a clientside callback to access the ID of the component that triggered the callback.
In this example, we display the triggered_id
in the app when a button is clicked.
This example has not been ported to Julia yet - showing the Python version instead.
Visit the old docs site for Julia at: https://community.plotly.com/c/dash/julia/20
from dash import Dash, html, Input, Output
app = Dash(prevent_initial_callbacks=True)
app.layout = html.Div(
[
html.Button("Button 1", id="btn1"),
html.Button("Button 2", id="btn2"),
html.Button("Button 3", id="btn3"),
html.Div(id="log"),
]
)
app.clientside_callback(
"""
function(){
console.log(dash_clientside.callback_context);
const triggered_id = dash_clientside.callback_context.triggered_id;
return "triggered id: " + triggered_id
}
""",
Output("log", "children"),
Input("btn1", "n_clicks"),
Input("btn2", "n_clicks"),
Input("btn3", "n_clicks"),
)
if __name__ == "__main__":
app.run()
New in 2.16
dash_clientside.set_props
allows you to update a Dash component property directly instead of updating it by having it as an output of a clientside callback. This can be useful if you have a non-Dash component (for example, a custom JavaScript component) that you want to update a Dash component property from, or if you want to implement custom functionality that is not available directly within Dash but that interacts with Dash.
The following example adds an event listener to the page. This event listener responds to the user pressing <kbd>Ctrl<kbd>+<kbd>R<kbd> by updating a dcc.Store
component’s data
. Another callback has the dcc.Store
component’s data
property as an input so runs each time it changes, outputting the updated data
to an html.Div
component.
This example has not been ported to Julia yet - showing the Python version instead.
Visit the old docs site for Julia at: https://community.plotly.com/c/dash/julia/20
from dash import Dash, html, dcc, Input, Output
app = Dash()
app.layout = html.Div(
[
html.Span(
[
"Press ",
html.Kbd("Ctrl"),
" + ",
html.Kbd("R"),
" to refresh the app's data",
]
),
dcc.Store(id="store-events", data={}),
html.Div(id="container-events"),
],
id="document",
)
app.clientside_callback(
"""
function () {
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.keyCode == 82) {
// Simulate getting new data
newData = JSON.stringify(new Date())
// Update dcc.Store with ID store-events
dash_clientside.set_props("store-events", {data: newData})
event.preventDefault()
event.stopPropagation()
return dash_clientside.no_update;
}
});
return dash_clientside.no_update;
}
""",
Output('document', 'id'),
Input('document', 'id'),
)
@app.callback(
Output('container-events', 'children'),
Input('store-events', 'data'),
prevent_initial_call=True
)
def handle_key_press(data):
return f"Current data value: {data}"
if __name__ == '__main__':
app.run(debug=True)
Notes about this example
dash_clientside.set_props
takes two arguments. The first is the ID of the Dash component to update. The second is an object with the name of the property to update as a key, and the value as the new value to update that property to. In this example dash_clientside.set_props("store-events", {data: newData})
updates the data
property of the Dash component with ID store-events
, with a new value of newData
, which here is a variable that contains a string representation of the current date and time.dash_clientside.no_update
, meaning it doesn’t update any Dash component specified as an Output
. The only update that happens to the page from the clientside callback is via dash_clientside.set_props
.Input
for the callback that adds the event listener is the ID of the app’s main container html.Div
. We use the id
property as this won’t change after our app loads, meaning this clientside callback only runs when the app loads.Output
is the same as the Input
, but it could be anything because we don’t update the Output
.There are a few limitations to keep in mind:
Promise
is returned.