> Note: Dash.NET is currently considered experimental. If your organization is interested in sponsoring Dash.NET, please get in touch.

# Clientside Callbacks

> 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 Dash.NET

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 Dash.NET.

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 .

For example, the following callback:

Can be rewritten to use JavaScript like so:

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 Dash.NET, the callback would now be written as:

    let callback =
        Callback.singleOut (
            [ "in-component1" @. Value
              "in-component2" @. Value ],
            "out-component" @. Value,
            , (fun (_: obj) (_: string) -> CallbackResultBinding.create (Dependency.create(null, null)) null)
            , ClientSideFunction = {
                Namespace = "clientside"
                FunctionName = "large_params_function"
            },
            PreventInitialCall = false
        )

The function (`fun (_: obj) (_:string)...`) is only to facilitate type inference and will not be called.

## A Simple Example

Below is an example of using clientside callbacks to update a graph in conjunction with a `Store` component. In the example, we update a `Store` 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".

First, create a JavaScript file `clientside.js` in `WebRoot\assets` with the following content:

    window.dash_clientside = Object.assign({}, window.dash_clientside, {
        figure: {
            set_scale: 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;
            }
        }
    });

    open Microsoft.Extensions.Logging
    open Giraffe
    open Dash.NET.Giraffe

    open Plotly.NET
    open Dash.NET
    open Dash.NET.DCC

    open FSharp.Data

    let externalStylesheets = [ "https://codepen.io/chriddyp/pen/bWLwgP.css" ]

    let [<Literal>] Csv = "https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv"
    let csv = CsvFile.Load(Csv).Cache()

    let availableCountries = csv.Rows |> Seq.map (fun r -> r.["country"]) |> Seq.distinct

    let layout =
        Html.div [
            Attr.children [
                Graph.graph "clientside-graph" []
                Store.store "clientside-figure-store" [ ]
                Html.text "Indicator"
                Dropdown.dropdown "clientside-graph-indicator" [
                    Dropdown.Attr.options [
                        DropdownOption.create "Population" "pop" false "Population"
                        DropdownOption.create "Life Expectancy" "lifeExp" false "Life Expectancy"
                        DropdownOption.create "GDP per Capita" "gdpPercap" false "GDP per Capita"
                    ]
                    Dropdown.Attr.value "pop"
                ]
                Html.text "Country"
                Dropdown.dropdown "clientside-graph-country" [
                    Dropdown.Attr.options (
                        availableCountries
                        |> Seq.map (fun country -> DropdownOption.create country country false country)
                    )
                    Dropdown.Attr.value "Canada"
                ]
                Html.text "Graph scale"
                RadioItems.radioItems "clientside-graph-scale" [
                    RadioItems.Attr.options (
                        [ "linear"; "log" ]
                        |> List.map (fun x -> RadioItemsOption.create x x false)
                    )
                    RadioItems.Attr.value "linear"
                ]
                Html.hr []
                Html.details [
                    Attr.children [
                        Html.summary [ Attr.children "Contents of figure storage" ]
                        Markdown.markdown "clientside-figure-json" []
                    ]
                ]
            ]
        ]

    let updateStoreData =
        let outputTarget = "clientside-figure-store" @. CustomProperty "data"
        Callback.singleOut (
            [ "clientside-graph-indicator" @. Value
              "clientside-graph-country" @. Value ],
            outputTarget,
            (fun (indicator: string) (country: string) ->
                let filtered = csv.Rows |> Seq.filter (fun r -> r.["country"] = country)
                let fig =
                    Chart.Scatter(
                        filtered |> Seq.map (fun r -> int r.["year"]),
                        filtered |> Seq.map (fun r -> decimal r.[indicator]),
                        StyleParam.Mode.Markers
                    )
                    |> Chart.withX_Axis(Axis.LinearAxis.init(Title = "year"))
                    |> Chart.withY_Axis(Axis.LinearAxis.init(Title = indicator))
                    |> GenericChart.toFigure
                outputTarget => fig
            ),
            PreventInitialCall = false
        )

    let setScale =
        Callback.singleOut (
            [ "clientside-figure-store" @. CustomProperty "data"
              "clientside-graph-scale" @. Value ],
            "clientside-graph" @. CustomProperty "figure",
            // This function is just a placeholder - it will not be called
            (fun (figure: obj) (scale: string) ->
                let dummyResult =
                    CallbackResultBinding.create (Dependency.create(null, null)) null
                dummyResult
            )
            , ClientSideFunction = {
                Namespace = "figure"
                FunctionName = "set_scale"
            },
            PreventInitialCall = false
        )

    let generatedFigureJson =
        let outputTarget = "clientside-figure-json" @. Children
        Callback.singleOut (
            "clientside-figure-store" @. CustomProperty "data",
            outputTarget,
            (fun (figure: obj) -> outputTarget => (string figure)),
            PreventInitialCall = false
        )

    [<EntryPoint>]
    let main argv =
      let dashApp =
        DashApp.initDefault()
        |> DashApp.appendCSSLinks externalStylesheets
        |> DashApp.appendScripts [ "./assets/clientside.js" ]
        |> DashApp.withLayout layout
        |> DashApp.addCallback generatedFigureJson
        |> DashApp.addCallback setScale
        |> DashApp.addCallback updateStoreData

      let config =
        { HostName = "localhost"
          LogLevel = LogLevel.Debug
          ErrorHandler = (fun ex -> text ex.Message) }

      DashApp.run [||] config dashApp

Note that, in this example, we are manually creating the `figure` by extracting the relevant data from the CSV. This is what gets stored in our `Store` component; expand the "Contents of figure storage" above to see exactly what is used to construct the graph. Notice a `layout` is already defined. So, we have to mutate the existing layout in our JavaScript code.

## Using Plotly Express to Generate a Figure

    open Microsoft.Extensions.Logging
    open Giraffe
    open Dash.NET.Giraffe

    open Plotly.NET
    open Dash.NET
    open Dash.NET.DCC

    open FSharp.Data

    let externalStylesheets = [ "https://codepen.io/chriddyp/pen/bWLwgP.css" ]

    let [<Literal>] Csv = "https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv"
    let csv = CsvFile.Load(Csv).Cache()

    let availableCountries = csv.Rows |> Seq.map (fun r -> r.["country"]) |> Seq.distinct

    let layout =
        Html.div [
            Attr.children [
                Graph.graph "clientside-graph" []
                Store.store "clientside-figure-store" [ ]
                Html.text "Indicator"
                Dropdown.dropdown "clientside-graph-indicator" [
                    Dropdown.Attr.options [
                        DropdownOption.create "Population" "pop" false "Population"
                        DropdownOption.create "Life Expectancy" "lifeExp" false "Life Expectancy"
                        DropdownOption.create "GDP per Capita" "gdpPercap" false "GDP per Capita"
                    ]
                    Dropdown.Attr.value "pop"
                ]
                Html.text "Country"
                Dropdown.dropdown "clientside-graph-country" [
                    Dropdown.Attr.options (
                        availableCountries
                        |> Seq.map (fun country -> DropdownOption.create country country false country)
                    )
                    Dropdown.Attr.value "Canada"
                ]
                Html.text "Graph scale"
                RadioItems.radioItems "clientside-graph-scale" [
                    RadioItems.Attr.options (
                        [ "linear"; "log" ]
                        |> List.map (fun x -> RadioItemsOption.create x x false)
                    )
                    RadioItems.Attr.value "linear"
                ]
                Html.hr []
                Html.details [
                    Attr.children [
                        Html.summary [ Attr.children "Contents of figure storage" ]
                        Markdown.markdown "clientside-figure-json" []
                    ]
                ]
            ]
        ]

    let updateStoreData =
        let outputTarget = "clientside-figure-store" @. CustomProperty "data"
        Callback.singleOut (
            [ "clientside-graph-indicator" @. Value
              "clientside-graph-country" @. Value ],
            outputTarget,
            (fun (indicator: string) (country: string) ->
                let filtered = csv.Rows |> Seq.filter (fun r -> r.["country"] = country)
                let fig =
                    Chart.Scatter(
                        filtered |> Seq.map (fun r -> int r.["year"]),
                        filtered |> Seq.map (fun r -> decimal r.[indicator]),
                        StyleParam.Mode.Markers
                    )
                    |> Chart.withX_Axis(Axis.LinearAxis.init(Title = "year"))
                    |> Chart.withY_Axis(Axis.LinearAxis.init(Title = indicator))
                    |> GenericChart.toFigure
                outputTarget => fig
            ),
            PreventInitialCall = false
        )

    let setScale =
        Callback.singleOut (
            [ "clientside-figure-store" @. CustomProperty "data"
              "clientside-graph-scale" @. Value ],
            "clientside-graph" @. CustomProperty "figure",
            // This function is just a placeholder - it will not be called
            (fun (figure: obj) (scale: string) ->
                let dummyResult =
                    CallbackResultBinding.create (Dependency.create(null, null)) null
                dummyResult
            )
            , ClientSideFunction = {
                Namespace = "figure"
                FunctionName = "set_scale"
            },
            PreventInitialCall = false
        )

    let generatedFigureJson =
        let outputTarget = "clientside-figure-json" @. Children
        Callback.singleOut (
            "clientside-figure-store" @. CustomProperty "data",
            outputTarget,
            (fun (figure: obj) -> outputTarget => (string figure)),
            PreventInitialCall = false
        )

    [<EntryPoint>]
    let main argv =
      let dashApp =
        DashApp.initDefault()
        |> DashApp.appendCSSLinks externalStylesheets
        |> DashApp.appendScripts [ "./assets/clientside.js" ]
        |> DashApp.withLayout layout
        |> DashApp.addCallback generatedFigureJson
        |> DashApp.addCallback setScale
        |> DashApp.addCallback updateStoreData

      let config =
        { HostName = "localhost"
          LogLevel = LogLevel.Debug
          ErrorHandler = (fun ex -> text ex.Message) }

      DashApp.run [||] config dashApp

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.

## Clientside Callbacks with Promises

Dash 2.4 and later supports clientside callbacks that return promises.

### Fetching Data Example

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 F# yet - showing the Python version instead.

> Visit the old docs site for F# at: https://community.plotly.com/c/dash/net/26

    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)

### Notifications Example

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 F# yet - showing the Python version instead.

> Visit the old docs site for F# at: https://community.plotly.com/c/dash/net/26

    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)

## Callback Context

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 F# yet - showing the Python version instead.

> Visit the old docs site for F# at: https://community.plotly.com/c/dash/net/26

    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()

## Set Props

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 `Ctrl`+`R` 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 F# yet - showing the Python version instead.

> Visit the old docs site for F# at: https://community.plotly.com/c/dash/net/26

    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.
- The clientside callback returns `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`.
- The `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.
- In this example, the `Output` is the same as the `Input`, but it could be anything because we don't update the `Output`.

## Limitations

There are a few limitations to keep in mind:

1. Clientside callbacks execute on the browser's main thread and will block rendering and events processing while being executed.
2. Clientside callbacks are not possible if you need to refer to global variables on the server or a DB call is required.
3. Dash.NET does not support asynchronous clientside callbacks and will fail if a `Promise` is returned.