How to disable a dcc.Interval from list of rendered AIO components

Hi, I’ve built an AIO component that renders a Tooltip and Popover above a styled Div element. When a user hover’s on the styled Div, the Tooltip will provide some arbitrary name above the Div; and when the Div is clicked on, a Popover will show some other arbitrary info above the same Div element.

The AIO component works fine, however, I’m rendering the AIO component for every item within a list. This list is updated using a dcc.Interval every 5 seconds. So, I have encountered the issue of opening either a Tooltip or Popover above one of the rendered AIO components and having the opened Tooltip or Popover display for as long as the dcc.Interval has not updated the list of items that renders each of the AIO components. Ideally, the opened Tooltip or Popover would remain in the opened state until the user has closed the Tooltip or Popover.

To accomplish this UX, I’m attempting to update the disabled state/property of the dcc.Interval to True when the user clicks on one of the rendered AIO components from the list. By disabling the Interval, a Tooltip or Popover should continue being rendered above its associated Div element until the user chooses to close the Popover or Tooltip.

However, after implementing this logic via a callback, I encounter the following error:

Screen Shot 2023-03-28 at 2.14.03 AM

Here’s the example of the AIO component which is called EndpointBlock:

import uuid
import logging
import dash_bootstrap_components as dbc
from dash import dcc, Input, Output, State, html, callback, no_update, MATCH, dash_table
from influxdb import InfluxDBClient
import psycopg2

class EndpointBlockAIO(html.Div):

    # A set of functions that create pattern-matching callbacks of the subcomponents
    class ids:
        block = lambda aio_id: {
            'component': 'EndpointBlock',
            'subcomponent': 'block',
            'aio_id': aio_id
        }
        tooltip = lambda aio_id: {
            'component': 'EndpointBlock',
            'subcomponent': 'tooltip',
            'aio_id': aio_id
        }
        popover = lambda aio_id: {
            'component': 'EndpointBlock',
            'subcomponent': 'popover',
            'aio_id': aio_id
        }

    # Make the ids class a public class
    ids = ids

    # Define the arguments of the All-in-One component
    def __init__(self, value, aio_id=None):

        # Allow developers to pass in their own `aio_id` if they're
        # binding their own callback to a particular component.
        if aio_id is None:
            aio_id = str(uuid.uuid4())

        # Define the component's layout
        super().__init__([  # Equivalent to `html.Div([...])`
            html.Div([],
                id=self.ids.block(aio_id),
                className="heatmap-block {0}".format('heatmap-red' if value['online_status'] == True else ''))
            , dbc.Tooltip(
                "{}".format(value["ip"]),
                id=self.ids.tooltip(aio_id),
                target=self.ids.block(aio_id),
                is_open=False,
                trigger=None,
                placement="top")
            , dbc.Popover(
                "Test Popover",
                id=self.ids.popover(aio_id),
                target=self.ids.block(aio_id),
                body=True,
                trigger="click",
                hide_arrow=True,
                placement="top")
        ])

    @callback(Output(ids.tooltip(MATCH), "is_open"),
              Input(ids.block(MATCH), "n_clicks"),
              State(ids.tooltip(MATCH), "is_open"))
    def toggle_endpoint_tooltips(n_clicks, is_open):
        if n_clicks:
            return not is_open
        else:
            return is_open

Here’s how the AIO component is used:

def subnet_card(subnet, values):
    blocks = []
    # sort pattern prevents blocks from appearing in different order every reload
    for index, value in enumerate(sorted(values, key=lambda endpoint: socket.inet_aton(endpoint["ip"])), start=1):
        blocks.append(EndpointBlockAIO(value))
    return html.Div([
        html.Div([
            subnet
        ], className="card-header"),
        html.Div([
            html.Div(blocks, className="subnet-heatmap", id="subnet-heatmap")
        ], className="card-body")
    ], className="card subnet-card m-2 p-0")

Here’s the callback that initiates the error mentioned above:

@callback(
    Output('dashboard_task_interval', 'disabled'),
    Input(EndpointBlockAIO.ids.block(MATCH), 'n_clicks'),
    State(EndpointBlockAIO.ids.tooltip(MATCH), 'is_open')
)
def toggle_task_interval_for_tooltip(n_clicks, is_disabled):
    if n_clicks:
        return not is_disabled
    else:
        is_disabled

Does anyone understand this error, and are there any recommendations on how to fix this?

hi @kchatman
I talked to my colleague about this.
They said that if you add a MATCH in the last callback output, that should solve it.

@adamschroeder Hi Adam, thanks for replying. Is it possible to add a MATCH statement even though the ‘dashboard_task_interval’ isn’t a part of the AIO component? If so, how would this be done?