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