Interactive .md

Advanced callback patterns and state management examples

Interactive .md

Advanced callback patterns and state management examples


Introduction

This page demonstrates advanced interactive patterns using Dash callbacks, state management, and component interactions. Learn how to build complex, responsive user interfaces with real-time updates.


Callback Basics

Callbacks are the heart of interactivity in Dash. They connect component properties and define how your app responds to user actions.

Simple Callback Example

A basic button that updates text:

:code: false Source code:

# File: docs/interactive-components/simple_callback.py

from dash import html, callback, Input, Output
import dash_mantine_components as dmc

component = html.Div([
    dmc.Title("Simple Callback Example", order=4, mb=10),
    dmc.Button(
        "Click Me!",
        id="simple-button",
        variant="filled",
        color="teal",
        mb=15
    ),
    dmc.Paper([
        html.Div(
            "Button not clicked yet",
            id="simple-output"
        )
    ], p="md", withBorder=True, radius="md")
])


@callback(
    Output("simple-output", "children"),
    Input("simple-button", "n_clicks"),
    prevent_initial_call=False
)
def update_output(n_clicks):
    if n_clicks is None:
        return "Button not clicked yet"
    return f"Button has been clicked {n_clicks} times!"

Multiple Inputs

Handle multiple input sources in a single callback:

:code: false Source code:

# File: docs/interactive-components/multiple_inputs.py

from dash import html, callback, Input, Output
import dash_mantine_components as dmc

component = html.Div([
    dmc.Title("Multiple Inputs Example", order=4, mb=10),
    dmc.Text("Enter two numbers to see their sum:", mb=10),

    dmc.Stack([
        dmc.NumberInput(
            label="First Number",
            id="num1-input",
            value=0,
            min=0,
            max=100
        ),
        dmc.NumberInput(
            label="Second Number",
            id="num2-input",
            value=0,
            min=0,
            max=100
        ),
        dmc.Paper([
            dmc.Text("Result:", size="sm", c="dimmed", mb=5),
            dmc.Title(
                "0",
                id="sum-output",
                order=3,
                c="teal"
            )
        ], p="md", withBorder=True, radius="md")
    ], gap="sm")
])


@callback(
    Output("sum-output", "children"),
    Input("num1-input", "value"),
    Input("num2-input", "value")
)
def calculate_sum(num1, num2):
    num1 = num1 or 0
    num2 = num2 or 0
    total = num1 + num2
    return f"{num1} + {num2} = {total}"

State Management

Use State to access component values without triggering the callback:

:code: false Source code:

# File: docs/interactive-components/state_example.py

from dash import html, callback, Input, Output, State
import dash_mantine_components as dmc

component = html.Div([
    dmc.Title("State Management Example", order=4, mb=10),
    dmc.Text(
        "Type your name and click submit. Notice the callback only fires when you click submit, not on every keystroke.",
        mb=10,
        c="dimmed",
        size="sm"
    ),

    dmc.Stack([
        dmc.TextInput(
            label="Enter Your Name",
            placeholder="John Doe",
            id="name-input-state"
        ),
        dmc.Button(
            "Submit",
            id="submit-btn-state",
            variant="filled",
            color="teal"
        ),
        dmc.Paper([
            html.Div(
                "Click submit to see your name",
                id="greeting-output"
            )
        ], p="md", withBorder=True, radius="md")
    ], gap="sm")
])


@callback(
    Output("greeting-output", "children"),
    Input("submit-btn-state", "n_clicks"),
    State("name-input-state", "value"),
    prevent_initial_call=True
)
def display_greeting(n_clicks, name):
    if not name:
        return "Please enter your name above!"

    return html.Div([
        dmc.Text(f"Hello, {name}! πŸ‘‹", size="lg", fw=500, c="teal"),
        dmc.Text(f"You've submitted this form {n_clicks} time(s).", size="sm", c="dimmed", mt=5)
    ])

Pattern Matching Callbacks

Create dynamic components with pattern-matching callbacks:

:code: false Source code:

# File: docs/interactive-components/pattern_matching.py

from dash import html, callback, Input, Output, State, ALL, MATCH
import dash_mantine_components as dmc
from dash_iconify import DashIconify

component = html.Div([
    dmc.Title("Pattern Matching Callbacks", order=4, mb=10),
    dmc.Text("Click 'Add Item' to dynamically create new inputs with pattern-matching callbacks.", mb=15, c="dimmed", size="sm"),

    dmc.Button(
        "Add Item",
        id="add-item-btn",
        variant="filled",
        color="teal",
        leftSection=DashIconify(icon="mdi:plus"),
        mb=15
    ),

    html.Div(id="items-container"),

    dmc.Paper([
        dmc.Text("Total of all items:", size="sm", c="dimmed", mb=5),
        dmc.Title("0", id="total-sum", order=3, c="teal")
    ], p="md", withBorder=True, radius="md", mt=15)
])


@callback(
    Output("items-container", "children"),
    Input("add-item-btn", "n_clicks"),
    State("items-container", "children"),
    prevent_initial_call=True
)
def add_item(n_clicks, children):
    children = children or []
    new_id = len(children)

    new_item = dmc.Group([
        dmc.NumberInput(
            label=f"Item {new_id + 1}",
            id={"type": "item-input", "index": new_id},
            value=0,
            min=0,
            style={"flex": 1}
        ),
        dmc.ActionIcon(
            DashIconify(icon="mdi:delete"),
            id={"type": "delete-btn", "index": new_id},
            variant="filled",
            color="red",
            size="lg",
            mt=25
        )
    ], mb=10)

    children.append(new_item)
    return children


@callback(
    Output("total-sum", "children"),
    Input({"type": "item-input", "index": ALL}, "value")
)
def calculate_total(values):
    total = sum(v or 0 for v in values)
    return f"Total: {total}"


@callback(
    Output("items-container", "children", allow_duplicate=True),
    Input({"type": "delete-btn", "index": ALL}, "n_clicks"),
    State("items-container", "children"),
    prevent_initial_call=True
)
def delete_item(delete_clicks, children):
    from dash import ctx

    if not ctx.triggered or not any(delete_clicks):
        return children

    # Find which delete button was clicked
    button_id = ctx.triggered_id
    if button_id:
        index_to_remove = button_id["index"]
        children = [child for i, child in enumerate(children) if i != index_to_remove]

    return children

Chained Callbacks

Connect multiple callbacks to create complex workflows:

:code: false Source code:

# File: docs/interactive-components/chained_callbacks.py

from dash import html, callback, Input, Output, State, dcc
import dash_mantine_components as dmc

# Sample data structure
data = {
    "USA": {
        "California": ["Los Angeles", "San Francisco", "San Diego"],
        "Texas": ["Houston", "Dallas", "Austin"],
        "Florida": ["Miami", "Orlando", "Tampa"]
    },
    "Canada": {
        "Ontario": ["Toronto", "Ottawa", "Hamilton"],
        "Quebec": ["Montreal", "Quebec City", "Laval"],
        "British Columbia": ["Vancouver", "Victoria", "Kelowna"]
    },
    "Mexico": {
        "Jalisco": ["Guadalajara", "Puerto Vallarta", "Zapopan"],
        "Nuevo LeΓ³n": ["Monterrey", "San Pedro", "Santa Catarina"]
    }
}

component = html.Div([
    dmc.Title("Chained Callbacks Example", order=4, mb=10),
    dmc.Text(
        "Select a country to populate states, then select a state to populate cities. Each selection triggers the next dropdown.",
        mb=15,
        c="dimmed",
        size="sm"
    ),

    dmc.Stack([
        dmc.Select(
            label="Country",
            placeholder="Select a country",
            id="country-dropdown",
            data=[{"label": country, "value": country} for country in data.keys()]
        ),
        dmc.Select(
            label="State/Province",
            placeholder="Select a country first",
            id="state-dropdown",
            data=[],
            disabled=True
        ),
        dmc.Select(
            label="City",
            placeholder="Select a state first",
            id="city-dropdown",
            data=[],
            disabled=True
        ),
        dmc.Paper([
            dmc.Text("Your Selection:", size="sm", c="dimmed", mb=5),
            html.Div(
                "Make selections above to see your full location",
                id="selection-output"
            )
        ], p="md", withBorder=True, radius="md")
    ], gap="sm")
])


@callback(
    Output("state-dropdown", "data"),
    Output("state-dropdown", "disabled"),
    Output("state-dropdown", "value"),
    Input("country-dropdown", "value")
)
def update_states(country):
    if not country:
        return [], True, None

    states = list(data[country].keys())
    state_options = [{"label": state, "value": state} for state in states]

    return state_options, False, None


@callback(
    Output("city-dropdown", "data"),
    Output("city-dropdown", "disabled"),
    Output("city-dropdown", "value"),
    Input("state-dropdown", "value"),
    State("country-dropdown", "value")
)
def update_cities(state, country):
    if not state or not country:
        return [], True, None

    cities = data[country][state]
    city_options = [{"label": city, "value": city} for city in cities]

    return city_options, False, None


@callback(
    Output("selection-output", "children"),
    Input("city-dropdown", "value"),
    State("state-dropdown", "value"),
    State("country-dropdown", "value")
)
def display_selection(city, state, country):
    if not all([country, state, city]):
        return "Make selections above to see your full location"

    return html.Div([
        dmc.Text("πŸ“ Your Location:", fw=500, mb=5),
        dmc.Text(f"{city}, {state}, {country}", size="lg", c="teal")
    ])

Loading States

Provide visual feedback during long-running operations:

:code: false Source code:

# File: docs/interactive-components/loading_states.py

from dash import html, callback, Input, Output, dcc
import dash_mantine_components as dmc
import time

component = html.Div([
    dmc.Title("Loading States Example", order=4, mb=10),
    dmc.Text(
        "Click the button to trigger a slow operation. Notice the loading indicator while processing.",
        mb=15,
        c="dimmed",
        size="sm"
    ),

    dmc.Button(
        "Process Data",
        id="process-btn",
        variant="filled",
        color="teal",
        mb=15
    ),

    dcc.Loading(
        id="loading-component",
        type="default",
        children=[
            dmc.Paper([
                html.Div(
                    "Click the button to start processing",
                    id="loading-output"
                )
            ], p="md", withBorder=True, radius="md")
        ]
    )
])


@callback(
    Output("loading-output", "children"),
    Input("process-btn", "n_clicks"),
    prevent_initial_call=True
)
def process_data(n_clicks):
    # Simulate a slow operation
    time.sleep(2)

    return html.Div([
        dmc.Alert(
            title="Processing Complete!",
            color="teal",
            children=[
                dmc.Text(f"Successfully processed request #{n_clicks}", mb=5),
                dmc.Text("This simulated a 2-second operation.", size="sm", c="dimmed")
            ]
        )
    ])

Callback Patterns Reference

Common Callback Patterns

Pattern 1: Single Input, Single Output
@callback(
    Output("output-id", "children"),
    Input("input-id", "value")
)
def update_output(input_value):
    return f"You entered: {input_value}"
Pattern 2: Multiple Inputs, Single Output
@callback(
    Output("result", "children"),
    Input("input1", "value"),
    Input("input2", "value")
)
def combine_inputs(val1, val2):
    return f"{val1} + {val2} = {val1 + val2}"
Pattern 3: Single Input, Multiple Outputs
@callback(
    Output("output1", "children"),
    Output("output2", "children"),
    Input("trigger", "n_clicks")
)
def update_multiple(n_clicks):
    return f"Clicks: {n_clicks}", f"Double: {n_clicks * 2}"
Pattern 4: Using State
@callback(
    Output("display", "children"),
    Input("submit-btn", "n_clicks"),
    State("input-field", "value")
)
def submit_form(n_clicks, value):
    if n_clicks is None:
        return "Click submit to see value"
    return f"Submitted: {value}"
Pattern 5: Pattern Matching with ALL
@callback(
    Output("summary", "children"),
    Input({"type": "dynamic-input", "index": ALL}, "value")
)
def aggregate_inputs(values):
    return f"Total: {sum(v or 0 for v in values)}"
Pattern 6: Pattern Matching with MATCH
@callback(
    Output({"type": "output", "index": MATCH}, "children"),
    Input({"type": "input", "index": MATCH}, "value")
)
def update_matching(value):
    return f"Value: {value}"

Advanced Techniques

Preventing Initial Calls

Prevent callbacks from firing on page load:

@callback(
    Output("output", "children"),
    Input("button", "n_clicks"),
    prevent_initial_call=True
)
def update(n_clicks):
    return f"Button clicked {n_clicks} times"

Circular Callbacks

Allow circular callback chains with allow_duplicate=True:

@callback(
    Output("value", "data", allow_duplicate=True),
    Input("increment", "n_clicks"),
    State("value", "data"),
    prevent_initial_call=True
)
def increment_value(n_clicks, current):
    return (current or 0) + 1

Determining Trigger

Find out which input triggered the callback:

from dash import ctx

@callback(
    Output("output", "children"),
    Input("btn1", "n_clicks"),
    Input("btn2", "n_clicks")
)
def update(btn1_clicks, btn2_clicks):
    trigger_id = ctx.triggered_id

    if trigger_id == "btn1":
        return "Button 1 was clicked"
    elif trigger_id == "btn2":
        return "Button 2 was clicked"

    return "No button clicked yet"

Background Callbacks

For long-running operations (requires diskcache or celery):

from dash import DiskcacheManager
import diskcache

cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)

@callback(
    Output("result", "children"),
    Input("submit", "n_clicks"),
    background=True,
    manager=background_callback_manager
)
def long_running_task(n_clicks):
    # Simulate long operation
    time.sleep(10)
    return "Task complete!"

Best Practices

1. Keep Callbacks Simple

Break complex logic into multiple callbacks:

βœ… Good:

@callback(Output("processed", "data"), Input("raw", "data"))
def process_data(raw):
    return process(raw)

@callback(Output("chart", "figure"), Input("processed", "data"))
def create_chart(processed):
    return make_figure(processed)

❌ Bad:

@callback(Output("chart", "figure"), Input("raw", "data"))
def do_everything(raw):
    processed = process(raw)
    return make_figure(processed)

2. Use State for Form Inputs

Use State to avoid triggering callbacks on every keystroke:

# Good for forms
@callback(
    Output("result", "children"),
    Input("submit-button", "n_clicks"),
    State("text-input", "value")
)

3. Validate Inputs

Always validate input values:

@callback(Output("output", "children"), Input("input", "value"))
def update(value):
    if value is None or value == "":
        return "Please enter a value"

    if not isinstance(value, str):
        return "Invalid input type"

    return f"Valid input: {value}"

4. Handle None Values

Check for None to handle initial callbacks:

@callback(Output("output", "children"), Input("input", "value"))
def update(value):
    if value is None:
        return "Waiting for input..."

    return f"Processing: {value}"

5. Use Proper IDs

Choose descriptive, unique IDs:

βœ… Good: "user-email-input", "submit-form-button" ❌ Bad: "input1", "btn"


Performance Tips

1. Memoization

Cache expensive computations:

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_computation(param):
    # Heavy processing here
    return result

2. Clientside Callbacks

Use JavaScript callbacks for simple UI updates:

app.clientside_callback(
    """
    function(n_clicks) {
        return n_clicks || 0;
    }
    """,
    Output("counter", "children"),
    Input("button", "n_clicks")
)

3. Partial Updates

Update only what changed:

@callback(
    Output("table", "data"),
    Input("refresh", "n_clicks"),
    State("table", "data")
)
def update_table(n_clicks, current_data):
    # Only update specific rows
    new_data = current_data.copy()
    new_data[0] = updated_row
    return new_data

Common Pitfalls

1. Circular Dependency

❌ Wrong:

@callback(Output("a", "value"), Input("b", "value"))
def update_a(b): return b

@callback(Output("b", "value"), Input("a", "value"))
def update_b(a): return a

βœ… Fixed:

@callback(
    Output("a", "value", allow_duplicate=True),
    Input("b", "value"),
    prevent_initial_call=True
)
def update_a(b): return b

2. Modifying Global State

❌ Wrong:

global_data = []

@callback(Output("out", "children"), Input("btn", "n_clicks"))
def update(n):
    global_data.append(n)  # Don't do this!
    return len(global_data)

βœ… Fixed:

@callback(
    Output("store", "data"),
    Output("out", "children"),
    Input("btn", "n_clicks"),
    State("store", "data")
)
def update(n, data):
    data = data or []
    data.append(n)
    return data, len(data)

Testing Callbacks

Example Test

from dash.testing import DashComposite

def test_callback(dash_duo):
    app = create_app()
    dash_duo.start_server(app)

    # Find input and click button
    input_elem = dash_duo.find_element("#my-input")
    input_elem.send_keys("test value")

    button = dash_duo.find_element("#submit-button")
    button.click()

    # Wait for callback to complete
    dash_duo.wait_for_text_to_equal("#output", "Expected text")

Next Steps


Happy coding! πŸš€


Source: /examples/interactive

Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs: