# 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* *Generated with dash-improve-my-llms*