Skip to content

Remove Callback Wildcard Restrictions (MATCH) #2462

Closed

Description

@milind

Is your feature request related to a problem? Please describe.
In trying to make my applications as modular as possible, I tend to use pattern-matching callbacks just about everywhere. There are many instances, however, where I need to be able to detect which component was triggered (via MATCH) and update a universal component.

Describe the solution you'd like
I would love for the restriction on the MATCH wildcard that requires the same number of MATCH values in the Input ID's and Output ID's to be lifted, allowing for more wildcards in the input than the output. It's my understanding that today's release of allow_duplicate in Dash's Output component should open the door to adding in the aforementioned solution.

Describe alternatives you've considered
The current solution for this use case is to use the ALL wildcard along with the callback context to filter down to the content that I'm ultimately interested in, but that solution does not scale especially well as it requires a lot of information to pass through the network, slowing things down.

Activity

  1. alexcjohnson commented on Mar 16, 2023

    @alexcjohnson
    Collaborator

    Thanks @milind

    @T4rk1n I think "any MATCH present in the inputs must be present in all the outputs too" is another flavor of the restriction "every output can be connected to only one callback" - it's just that these duplicates come from the same function. So in the same way as we do for multiple callback functions we should be able to relax that restriction when the output that's missing a MATCH specifies allow_duplicate. Seem reasonable?

    To make this concrete, I guess we're talking about callbacks that would look like:

    @callback(
        Output("out", "children"),
        Input({"id": MATCH}, "n_clicks"),
        State({"id": MATCH}, "id"),
        prevent_initial_call=True
    )
    def cb(_, _id):
        return f"you clicked the button with id {_id['id']}"
  2. T4rk1n commented on Mar 17, 2023

    @T4rk1n
    Contributor

    I think the validation should be lifted entirely for Output, it makes MATCH awkward to use and I remember removing the validation without any breakage.

  3. alexcjohnson commented on Mar 17, 2023

    @alexcjohnson
    Collaborator

    It would have exactly the same kind of breakage scenarios as other duplicate outputs, an ambiguity when two callbacks result from the same stimulus. Which is why I think allowing it the same way (with allow_duplicate) makes most sense.

  4. olejorgenb commented on Sep 14, 2023

    @olejorgenb

    I would also like this restriction to be lifted. I'm trying out a convention where callbacks which modify database state triggers a re-render using a "trigger" output. This does not work with this restriction in place.

    For searchability - this is the error message:

    `Input` / `State` wildcards not in `Output`s
    
    or
    
    Output X
    does not have MATCH wildcards on the same keys as
    Output Y
    MATCH wildcards must be on the same keys for all Outputs. ALL  wildcards need not match, only MATCH
    

    Someone else mentioned that they would like to do partial update based on a MATCH callback. On the surface this also sounds like a reasonable use-case (with the disclaimer that I haven't used partial update enough to really know it makes sense).

    EDIT: Thinking a bit further - any scenerio where doing something to a component[1] should trigger a not-local-to-that-component update become cumbersome to implement. Only workaround (other than the one mentioned by OP) I can see is to add dummy outputs per component and route these to the callback implementing the global update.

    [1] Which there can be an arbitrary number of

  5. RenaudLN commented on Nov 2, 2023

    @RenaudLN
    Contributor

    A current workaround to avoid the network cost of passing ALL to the callback would be:

    • to use ALL in a clientside callback, storing the information about the triggered input(s) at a level with as many MATCH as the desired Output
    • use the above store in a serverside callback to update the desired Output

    Something like:

    clientside_callback(
        """() => {
            return dash_clientside.callback_context.triggered.map(t => JSON.parse(t.prop_id.split(".")[0]))
        }"""
        Output("input_store", "data"),
        Input({"type": "btn", "id": ALL}, "n_clicks"),
    )
    
    @callback(
        Output("output", "children"),
        Input("input_store", "data"),
    )
    def myfunc(triggered_inputs_data):
        # ...
  6. pitris90 commented on Feb 7, 2024

    @pitris90

    I would appreciate this restriction lifted too, as it would simplify my callbacks. I was so surprised it doesnt work when I tried to write similiar callback as you did.

  7. wilecoyote2015 commented on Feb 20, 2024

    @wilecoyote2015

    I second the request, as this limitation if often problematic for our use cases.

  8. alvarodemig commented on Mar 6, 2024

    @alvarodemig

    I agree. The current behavior is counterintuitive, as you can have the same output in multiple callbacks, and this would simplify many patterns.

  9. mmarfat commented on Apr 3, 2024

    @mmarfat

    Any updates on this?

  10. lunathanael commented on Apr 16, 2024

    @lunathanael

    Any updates on this?

  11. ndrezn commented on Apr 21, 2024

    @ndrezn
    Contributor

    Plotly team is looking into this as part of a future Dash release!

  12. deadkex commented on May 15, 2024

    @deadkex

    Another example use case:

    @callback(
        Output("notifications_container", "children", allow_duplicate=True),
        Output({"id": MATCH, "type": "grid"}, "rowData", allow_duplicate=True),
        Input({"id": MATCH, "type": "grid"}, "cellValueChanged")
    )
    def on_edit_grid_callback(event):
        return on_edit_grid(event)
  13. 4 remaining items

  14. mbworth commented on Oct 31, 2024

    @mbworth

    This certainly makes sense for no Output specified but I think there could still be trouble if an Output is specified without MATCH. In the below example if, for whatever reason, the set of input triggers all occur at once (for instance from an Output({"id": ALL, "type": "store"}, "data") event), which callback writes to children of the notification container?

    @callback(
        Output("notifications_container", "children", allow_duplicate=True),
        Input({"id": MATCH, "type": "store"}, "data")
    )
    def on_edit_grid_callback(event):
        return on_edit_grid(event)
  15. vhorvath commented on Jan 28, 2025

    @vhorvath

    Plotly team, where are you on this, this issue has been around for almost two years now.

    To those waiting on a solution: pattern matching works with ALL for clickable inputs, but doesn't with MATCH. There is workaround with ALL:

    @callback(
        Output("notifications_container", "children"),
        Input({"id": ALL, ...}, "n_clicks"),
        prevent_initial_call = True
    )
    def cb(n_clicks):
        # This only needed for preventing the initial trigger, which does happen despite `prevent_initial_call` being set
        if not any(n > 0 for n in n_clicks):
            raise PreventUpdate
    
        # Use dash.ctx to identify the item fired the callback
        if not ctx.triggered_id:
            raise PreventUpdate
    
        triggered_item_id = ctx.triggered_id["id"]
        ...
  16. ndrezn commented on Jan 28, 2025

    @ndrezn
    Contributor

    For now this is on the backlog for the Plotly team as we've been focused on the Dash 3.0.0 release with a set of new changes (component typing, hooks, and more) but looking forward this is an improvement I'd love to tackle -- not to say when it will ship, but it's high on the priority list as we look forward for Dash 🙂

  17. 12rambau commented on Dec 12, 2025

    @12rambau
    Contributor

    Hi Plotly team, for me it's not "a limitation" it's preventing me to scale my application any further, I try to ship as much as possible all-in-one objects and the fact that they cannot interact with "general" element make them completely unusable.

  18. 12rambau commented on Jan 15, 2026

    @12rambau
    Contributor

    FYI typical use case is having a single dcc.Location in the app that all the all-in-One object need to modify according to what have been selected. I'm facing since this morning a bug that was impossible to detect where I update the url from within and AIO component with:

        @callback(
            Output(ids.url(MATCH), "search"),
            Input(ids.state(MATCH), "data"),
            Input(ids.map_state(MATCH), "data"),
            State(ids.url(MATCH), "id"),
        )
        @staticmethod
        def update_url(nav_data: dict | None, map_data: dict | None, url_id: dict) -> str | NoUpdate:
            """Update the url."""
            # Skip URL updates for backend pages
            if "backend" in url_id.get("aio_id", ""):
                return dash.no_update
    
            # early exit if no data is available
            if nav_data is None or map_data is None:
                return dash.no_update
    
            # Merge data and create state
            combined_data = {**nav_data, **map_data}
            state = map_state.GlobalMapState.from_data(combined_data)
            return state.url_params

    But it never triggers the global :

    @callback(
        Output("telemetry_tracker", "children", allow_duplicate=True),
        Input("url", "search"),
        State("url", "pathname"),
        State("magic_session", "data"),
        prevent_initial_call="initial_duplicate",
    )
    def track_update(search, pathname, session_data):
        """Track page update with session ID."""
        print("Tracking page update...")
        for route in dash.page_registry.keys():
            pattern = route_to_pattern(route)
            if session_data and session_data.get("session_id") and pattern.match(pathname):
                parameters = pattern.match(pathname).groupdict()
                kwargs = {"parameters": parameters, "query": parse_qs(search.lstrip("?"))}
                telemetry.add_page_update(session_data["session_id"], route.split(".")[-1], **kwargs)
                break
        return str(time.time())

    becasue they are not pointing to the same Location input, even though visually there is obviously only 1 single url in my browser.

  19. 12rambau commented on Mar 29, 2026

    @12rambau
    Contributor

    For those that are still facing this issue I found a workaround using a little trick: I create a callback with the same input and output I use the set_props fonction inside it to target exact ids and I prevent the update at the end.

    from urllib.parse import urlencode
    
    @callback(
        Output(ids.map_state(MATCH), "id"),
        Input(ids.state(MATCH), "data"),
    )
    def update_url(data: dict | None):
        """Update the url."""
        set_props("url", {"search": f"?{urlencode(data)}"})
    
        return dash.no_update
  20. added a commit that references this issue on Apr 22, 2026
    adad025
  21. added 5 commits that reference this issue on Apr 22, 2026
    1fc9b24
    931078d
    5ed8ded
    90540e5
    2937293
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2considered for next cyclefeaturesomething new

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions