dmc.SegmentedControl fires n_clicks when just value is needed

I have somewhat of a complex problem. my apologies if this is a bit esoteric.

I have a game simulator that pulls from data and generates a random score, with a dmc.Button() firing this callback with n_clicks. The button works fine. Here is the general idea of the webapp:

Where “DICE” pulls two random teams and “PLAY” in essence pulls two random scores from a matchup between the two teams.

The problem: in addition to the above, I’d like to add a dmc.SegmentedControl, where the user can guess “HOME” or “AWAY” and generate a text output if they’ve won or lost.

However, when I add this to my callback, dmc.SegmentedControl fires the n_clicks property from “PLAY” button, which messes with the functionality. I’d just like it to assign a value which can be checked during the callback function.

I can post code if needed, but wanted to ask first and foremost if Segmented Control is what I’m after here or if there is another way of achieving my goal.

Thanks in advance!

The segmentedControl should work as you expect. Need to see a minimal example to see what’s going on.

ah okay. Here goes…

The following explains the callback in question. Further below I will post the callback function.

  • The components accessed by state are the team names; these work as expected.
  • There are two inputs, one of the “PLAY” button that simulates a match (comp7), the other the Segmented Control switch that checks if the user guessed right. They are in the same callback because they both concern comp7’s functionality… is that right?
  • Output has three: one is the left score, one is the left score, and the third is the text that says “you’ve won!”.

Here is the code:

@app.callback(
        Output("PAP_ms10_2", "children"),
        Output("PAP_ms10_3", "children"),
        Output("PAP_s5_1", "children"),

        Input("comp7_SELECTclick_playbutton", "n_clicks"),
        Input("comp5p0_SELECTclick_homeoraway", "value"),

        State("comp0_SELECTselect_home", "value"),
        State("comp1_SELECTselect_away", "value")
    )

The callback function is bare bones basic:

  • the first if statement keeps the outputs at 0 on load, which is the intended behavior
  • the else statement performs the random operation. ** this is what the switch is doing but it shouldn’t be.**
  • based on the matrix of home won/away won and user guessed home/guessed away, returns the corresponding output

It looks messier than it is. Line 7 is the question - what I think is happening is that, after n_clicks is no longer None (it’s been clicked at least once), any input to the callback fires every input, and since n_clicks is now an integer, it returns the randomization operation.

    def nice(nclicks, homeoraway_switch, home_team, away_team):
        if not nclicks:
            return "0", "0", ""

        else:

            print(nclicks) #this is finnicky part

            home_score = random.choice(home[home["team_long_name"]== home_team]["home_team_goal"].values)
            away_score = random.choice(home[home["team_long_name"]== away_team]["home_team_goal"].values)
           
            if home_score > away_score:
                if homeoraway_switch == "HOME":
                    return dmc.Text(f"{home_score}", c=color_home, className="nv e"), dmc.Text(f"{away_score}", c=color_home, className="nv e"), dmc.Text("You win!(0)")
                elif homeoraway_switch == "AWAY":
                    return dmc.Text(f"{home_score}", c=color_home, className="nv e"), dmc.Text(f"{away_score}", c=color_home, className="nv e"), dmc.Text("You lose(1)!")

            elif home_score < away_score: 
                if homeoraway_switch == "HOME":
                    return dmc.Text(f"{home_score}", c=color_away, className="nv e"), dmc.Text(f"{away_score}", c=color_away, className="nv e"), dmc.Text("You lose(2)!")
                elif homeoraway_switch == "AWAY":
                    return dmc.Text(f"{home_score}", c=color_away, className="nv e"), dmc.Text(f"{away_score}", c=color_away, className="nv e"), dmc.Text("You win(3)!")

            elif home_score == away_score:
                return dmc.Text(f"{home_score}", c="gray", className="nv e"), dmc.Text(f"{away_score}", c="gray", className="nv e"), dmc.Text("One point. (4)")

This is the best I can do in terms of an MRE, but let me know if the gist is unclear!

Edit: added some summary above this last block of code. It seems to be a question of SegmentedControl firing the nclicks from button when it’s part of the same callback.

Hi @plotmaster422

When debugging or asking for help on the forum, it’s best to make a complete minimal example that reproduces the issue. By that, I mean one that can be copied, pasted and run locally, not just a callback.

So your theory is that the segmented control is firing the n_clicks of a button. That would be a huge bug. I can’t reproduce it. Here is an example of a how to make a minimal example for this issue. Try running this:


import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, Input, Output, ctx
_dash_renderer._set_react_version("18.2.0")

app = Dash(external_stylesheets=dmc.styles.ALL)

layout = dmc.Stack([
    dmc.Button("button", id="button", n_clicks=0),
    dmc.SegmentedControl(id="segmented", data=["A", "B"], value="A"),
    dmc.Text(id="out"),
], w=200)

app.layout = dmc.MantineProvider( layout)

@app.callback(
    Output("out", "children"),
    Input("button", "n_clicks"),
    Input("segmented", "value")
)
def update(n, seg):
    if ctx.triggered_id == "button":
        return f"button clicked {n}"
    if ctx.triggered_id == "segmented":
        return f"segment {seg} selected"


if __name__ == "__main__":
    app.run(debug=True)


I was unaware of the ctx attribute, but when I added this in, it runs perfectly. Does that mean it’s a bug or I just wasn’t using the correct attribute?

Noted on the MRE. I will do so next time. The answer in context for later users (copy and paste works):

import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, Input, Output, ctx
_dash_renderer._set_react_version("18.2.0")

import random

app = Dash(external_stylesheets=dmc.styles.ALL)

# # # # # # # # # lyt
comp0 = dmc.Card(
    [
        dmc.Button(
            "PLAY",
            color="rgb(8,81,156)",
            size="xl", 
            radius="xl",
            id="comp7_SELECTclick_playbutton"
            )
    ],
    className="t s7",
)

comp1 = dmc.Paper(
    [
        dmc.Text(
            [
                
            ],
            id="PAP_ms10_2",
            className="t ms10_0 nv e"
        ),
        dmc.Text(
            [
                dmc.Text(
                    "-", 
                size="xl", 
                className=" nv_m e"
                )
            ],
            className="t ms10_1 nv e"
        ),
        dmc.Text(
            [

            ],
            id="PAP_ms10_3",      
            className="t ms10_2 nv e"
        ),
    ],
    withBorder=True,
    radius="md",
    className="t ms10",
)

comp2 = dmc.Card(
    [
        dmc.SegmentedControl(
            data=["HOME", "AWAY"],
            value="HOME",
            id="comp5p0_SELECTclick_homeoraway"
        ),
        dmc.Text(
            [

            ],
            id="PAP_s5_1",
        )
    ],
    className="t s5"
)

lyt = dmc.MantineProvider([
    comp0,
    comp1,
    comp2
])

# # # # # # # # # # # callback
@app.callback(
    Output("PAP_ms10_2", "children"),
    Output("PAP_ms10_3", "children"),
    Output("PAP_s5_1", "children"),

    Input("comp7_SELECTclick_playbutton", "n_clicks"),
    Input("comp5p0_SELECTclick_homeoraway", "value"),

)
def nice(nclicks, homeoraway_switch):

    if nclicks is None:
        return "0", "0", ""

    else:
        if ctx.triggered_id== "comp7_SELECTclick_playbutton":

            print(nclicks)

            home_score = random.choice(range(0, 30))
            away_score = random.choice(range(0, 30))

            if home_score > away_score:
                if homeoraway_switch == "HOME":
                    return dmc.Text(f"{home_score}"), dmc.Text(f"{away_score}"), dmc.Text("You win!(0)")
                elif homeoraway_switch == "AWAY":
                    return dmc.Text(f"{home_score}"), dmc.Text(f"{away_score}"), dmc.Text("You lose(1)!")
            elif home_score < away_score: 
                if homeoraway_switch == "HOME":
                    return dmc.Text(f"{home_score}"), dmc.Text(f"{away_score}"), dmc.Text("You lose(2)!")
                elif homeoraway_switch == "AWAY":
                    return dmc.Text(f"{home_score}"), dmc.Text(f"{away_score}"), dmc.Text("You win(3)!")
            elif home_score == away_score:
                return dmc.Text(f"{home_score}", c="gray", className="nv e"), dmc.Text(f"{away_score}", c="gray", className="nv e"), dmc.Text("One point. (4)")
        
        elif ctx.triggered_id == "comp5p0_SELECTclick_homeoraway":
            return "0", "0", ""
        
# # # # # # # # # # # # run

app.layout = lyt

if __name__ == "__main__":
    app.run(debug=True, port=9999)

Thank you for your help, you are a lifesaver!

Glad it works for you :tada:

And no, it’s not a bug. A change to any Input in a callback will trigger the callback. a State will make data available in the callback function, but doesn’t trigger the callback when it changes.

1 Like