Interactive .md
Advanced callback patterns and state management examples
---
.. 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*
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:
- /examples/interactive/llms.txt β LLM-friendly documentation
- /sitemap.xml
- /robots.txt