Dynamic Callback Resetting All Slider Values

Hello,

I am making dynamic filter inputs as seen gif picture below. There is a dropdown with some numbers in it and a slider for choosing range. Dropdowns are setting minimum value of RangeSliders. In addition, I can add another dropdown and slider. My problem is, all RangeSlider I setted values resetting when I add a new filter. For example, in the first filter I’m choosing 1 from first dropdown after that the minimum value of RangeSlider setting 1. And I’m choosing range from RangeSlider. After that, I’m adding a new filter but my first RangeSlider range resetting. I don’t want to reset it. It should stay in the range I chose. What should I do for it?

dash2

Here is my code:

import dash
from dash.dependencies import Input, Output, State, ALL, MATCH
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import json

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div(
    [
        html.Div(id="filters",
                 children=[
                     html.Button("Add filter", id="add", n_clicks=0)
                 ])
    ]
)


@app.callback(
    Output("filters", "children"),
    [
        Input("add", "n_clicks"),
        (Input({"type": "remove", "index": ALL}, "n_clicks"))
    ],
    State("filters", "children")
)
def edit(n_clicks, _, children):
    input_id = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
    if "index" in input_id:
        delete_chart = json.loads(input_id)["index"]
        children = [chart for chart in children if "'index': " + str(delete_chart) not in str(chart)]

    else:
        new = html.Div(
            [dbc.Button("X", id={"type": "remove", "index": n_clicks}, n_clicks=0, outline=True, color="danger",
                        style={"height": "36px"}),
             html.Div(dcc.Dropdown(id={"type": "dropdown", "index": n_clicks},
                                   placeholder="Choose min value:",
                                   options=[{'label': i, 'value': i} for i in [1, 2, 3, 4, 5]],
                                   clearable=True),
                      style={"width": "30%", "display": "inline-block",
                             "margin-left": "20px"}),
             html.Div(dcc.RangeSlider(id={"type": "slider", "index": n_clicks},
                                      tooltip={'always_visible': True, 'placement': 'bottom'},
                                      step=0.1,
                                      disabled=True),
                      style={"width": "50%"})],
            className="d-flex flex-row mt-4")

        children.append(new)

    return children


@app.callback([Output({'type': 'slider', 'index': MATCH}, 'min'),
               Output({'type': 'slider', 'index': MATCH}, 'max'),
               Output({'type': 'slider', 'index': MATCH}, 'marks'),
               Output({'type': 'slider', 'index': MATCH}, 'value'),
               Output({'type': 'slider', 'index': MATCH}, 'disabled')],
              [Input({'type': 'dropdown', 'index': MATCH}, 'value')])
def update_slider(value):
    if value:
        min = value
        max = 20
        marks = {str(min): min, str(max): max}
        new_value = [min, max]
        disabled = False

        return min, max, marks, new_value, disabled


if __name__ == "__main__":
    app.run_server(debug=False, port=4444)

I solved my own problem. I just updated the update_slider as below.

@app.callback([Output({'type': 'slider', 'index': MATCH}, 'min'),
               Output({'type': 'slider', 'index': MATCH}, 'max'),
               Output({'type': 'slider', 'index': MATCH}, 'marks'),
               Output({'type': 'slider', 'index': MATCH}, 'value'),
               Output({'type': 'slider', 'index': MATCH}, 'disabled')],
              [Input({'type': 'dropdown', 'index': MATCH}, 'value')],
              State({'type': 'slider', 'index': MATCH}, 'value'))
def update_slider(value, slider_value):
    if value:
        min = value
        max = 20
        marks = {str(min): min, str(max): max}
        disabled = False

        if slider_value == [min, max]:
            new_value = [min, max]

        else:
            new_value = slider_value

        return min, max, marks, new_value, disabled

This solved my problem for now, but if you know more efficient solution please share with me, thank you :slight_smile:

1 Like

I believe that the reason why you had this issue is because you are rewriting all the RangeSlider components when you call the edit callback. Since you don’t have a default value for the component assigned, it takes the [min, max] values instead. Your solution fixes this exact problem.

You might be interested in the new All-In-One components for this kind of complex interactions among components. It makes it easier to encapsulate this kind of pattern.

2 Likes

I just heard All-In-One Components. It looks like very useful and now I have to learn it too :smiley: Thank you for your suggestion.