# Interactive .md

> Advanced callback patterns and state management examples

**Site index:** [https://boilerplate.2plot.dev/llms.txt](https://boilerplate.2plot.dev/llms.txt) — every page on this site, as Markdown.  
**Network index:** [https://2plot.dev/llms.txt](https://2plot.dev/llms.txt) — The 2plot network; start here to discover sibling sites.  
**Sibling sites:** 8 more in The 2plot network — listed in the site index above.  
**Sitemap:** https://boilerplate.2plot.dev/sitemap.xml  


---

.. llms_copy::Interactive Components

.. toc::

### 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:

.. exec::docs.interactive-components.simple_callback
    :code: false
Source code:


```python
# 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:

.. exec::docs.interactive-components.multiple_inputs
    :code: false
Source code:


```python
# 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:

.. exec::docs.interactive-components.state_example
    :code: false
Source code:


```python
# 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:

.. exec::docs.interactive-components.pattern_matching
    :code: false
Source code:


```python
# 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:

.. exec::docs.interactive-components.chained_callbacks
    :code: false
Source code:


```python
# 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:

.. exec::docs.interactive-components.loading_states
    :code: false
Source code:


```python
# 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

```python
@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

```python
@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

```python
@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

```python
@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

```python
@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

```python
@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:

```python
@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`:

```python
@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:

```python
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`):

```python
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:**
```python
@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:**
```python
@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:

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

#### 3. Validate Inputs

Always validate input values:

```python
@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:

```python
@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:

```python
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:

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

#### 3. Partial Updates

Update only what changed:

```python
@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:**
```python
@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:**
```python
@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:**
```python
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:**
```python
@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

```python
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

- **Data Visualization** - Create interactive charts
- **AI Integration** - Make your components AI-friendly
- **Getting Started** - Learn the basics

---

Happy coding! 🚀


---

*Source: /examples/interactive*
