Sync zoom range of the plots then set url - strange callback trigger

I have a use case to synchronize zoom range of 3 plots and also update to URL so the states can be saved in the bookmark.

I have the sync part working. But if I add a callback to change url, some strange trigger will happen to update the plot and alter their layout.

dash.ctx shows nothing.
image

Is this a bug?

import json
from urllib.parse import urlencode

import dash
import numpy as np
import pandas as pd
import plotly.express as px
from dash import Dash, dcc, html
from dash.dependencies import ALL, Input, Output, State

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
# make a sample data frame with 6 columns
np.random.seed(0)  # no-display
df = pd.DataFrame({"Col " + str(i + 1): np.random.rand(30) for i in range(6)})
df["time"] = pd.to_datetime(df.index * 20 + 1661573974, unit="s")

figs = []
for i in [0, 1, 2]:
    fig = px.scatter(
        df, x=df["time"], y=df["Col " + str(i + 1)], text=df.index
    )
    fig.layout.autosize = False
    figs.append(fig)


app.layout = html.Div(
    [
        dcc.Location(id="url"),
        dcc.Store(id="store-first-load-check"),
        dcc.Input(
            id={"type": "input", "id": "xrange"},
            # value='{"xaxis.range[0]": "2022-08-27 04:23:14.0247",  "xaxis.range[1]": "2022-08-27 04:23:41.4052"}',
            value="",
            size="80",
            type="text",
        ),
        html.Div(
            dcc.Graph(
                id={"type": "synced_graph", "id": "g1", "index": 0},
                config={"displayModeBar": True},
                figure=figs[0],
            )
        ),
        html.Div(
            dcc.Graph(
                id={"type": "synced_graph", "id": "g2", "index": 1},
                config={"displayModeBar": True},
                figure=figs[1],
            ),
        ),
        html.Div(
            dcc.Graph(
                id={"type": "synced_graph", "id": "g3", "index": 2},
                config={"displayModeBar": True},
                figure=figs[2],
            ),
        ),
    ],
    className="row",
)


if True:

    @app.callback(
        Output("url", "search"),
        [Input({"type": "input", "id": "xrange"}, "value")],
    )
    def change_url(value):
        d = {"xrange": value}
        return "?" + urlencode(d)


# this callback defines 3 figures
# as a function of the intersection of their 3 selections
@app.callback(
    Output({"type": "synced_graph", "index": 0, "id": "g1"}, "figure"),
    Output({"type": "synced_graph", "index": 1, "id": "g2"}, "figure"),
    Output({"type": "synced_graph", "index": 2, "id": "g3"}, "figure"),
    Output({"type": "input", "id": "xrange"}, "value"),
    Output("store-first-load-check", "data"),
    Input({"type": "synced_graph", "index": ALL, "id": ALL}, "relayoutData"),
    Input({"type": "input", "id": "xrange"}, "value"),
    State("store-first-load-check", "modified_timestamp"),
    State({"type": "synced_graph", "index": 0, "id": "g1"}, "figure"),
    State({"type": "synced_graph", "index": 1, "id": "g2"}, "figure"),
    State({"type": "synced_graph", "index": 2, "id": "g3"}, "figure"),
)
def callback(relayouts, text_value, ts, figure1, figure2, figure3):
    # determine which input fired
    component_id_property = '{"type":"input","id":"xrange"}.value'  # no space
    if ts is None and text_value:  # first load
        relayout = json.loads(text_value)
        triggered_index = "text"
    elif component_id_property in dash.ctx.triggered_prop_ids:
        relayout = json.loads(text_value)
        triggered_index = "text"
    else:
        if dash.ctx.triggered_id is None:
            raise dash.exceptions.PreventUpdate()
        triggered_index = dash.ctx.triggered_id["index"]
        # identify which relayoutData to use
        relayout = relayouts[triggered_index]

    print(relayout)
    print(triggered_index)
    print(dash.ctx.triggered_prop_ids)
    outputs = []
    for index, fig in enumerate([figure1, figure2, figure3]):
        if relayout == {"autosize": True}:
            # fig["layout"]["xaxis"]["autorange"] = True
            raise dash.exceptions.PreventUpdate()

        if triggered_index == index:
            outputs.append(dash.no_update)
            continue

        try:
            fig["layout"]["xaxis"]["range"] = [
                relayout["xaxis.range[0]"],
                relayout["xaxis.range[1]"],
            ]
            fig["layout"]["xaxis"]["autorange"] = False
        except (KeyError, TypeError):
            fig["layout"]["xaxis"]["autorange"] = True
        outputs.append(fig)

    if triggered_index == "text":
        outputs.append(dash.no_update)
    else:
        outputs.append(json.dumps(relayout, indent=2))

    return tuple(outputs) + (False,)


if __name__ == "__main__":
    app.run_server(debug=True)