Location object update from callback does not fire an input location callback

Hi everyone.
I cannot find a way to “internal redirect” the user without refreshing the page, after the pressing of a button and checking some backend rules (inside the callback).

Imagine having a page (/my_table/new) with some form elements and a button. You want to submit it via the button and after creating an object in your database you want to change the location of the browser back to “/my_table” without refreshing the page.

You can a make the above scenario by placing a dcc.Location OUTPUT object to send the new pathname. You will see the browser location changing, but here is the problem. If you place another dcc.Location INPUT object to catch this browser’s change, this will never fire.

If you try the same with a dcc.Link everything works fine, including the INPUT location rule. But in that case you cannot check the form elements via a callback…

I am sending a code example to reproduce the problem.

Could anyone help me with this? Is there a possible via to do it?

Thank you!

from dash import Dash, no_update
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

app = Dash(__name__)

app.layout = html.Div([
    dcc.Location(id='url-input'),
    dcc.Location(id='url-output', refresh=False),
    html.Button("Click me", id="button-a"),
    dcc.Link("Click me", href='/link-a'),
    html.Div('', id='clicks'),
]
)


@app.callback(Output('clicks', 'children'), [Input('url-input', 'pathname')])
def update_url_by_component_a(pathname):
    print("pathname=", pathname)
    return f"{pathname}"


@app.callback(Output('url-output', 'pathname'), [Input('button-a', 'n_clicks')])
def update_url_by_component_a(value):
    if value:
        print("value=", value)
        return f"/clicks-{value}"
    return no_update


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

Here’s an example of “internal redirect” feature.
I think the key is to use the same dcc.Location instance

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ALL
import json

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

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    # represents the URL bar, doesn't render anything
    dcc.Location(id='url', refresh=False),

    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
    html.Br(),
    dcc.Link('Navigate to "/form1"', href='/form1'),
    html.Br(),
    dcc.Link('Navigate to "/form2"', href='/form2'),

    # content will be rendered in this element
    html.Div(id='page-content')
])


def default(pathname):
    return html.Div([
        html.H3('You are on page {}'.format(pathname))
    ])


def page1():
    return html.Div([
        html.H3('You are on page 1')
    ])


def page2():
    return html.Div([
        html.H3('You are on page 2')
    ])


def create_form1():
    title = html.Div([
        html.H3("Form1")
    ])
    textarea = html.Div([
        html.P("Form1 text"),
        dcc.Textarea(
            id={'type': 'text-form', 'name': 'form1'},
            style={'width': '100%', 'height': 200},
        ),
        html.Button(
            'Submit', id={'type': 'form-button', 'name': 'form1'}, n_clicks=0)
    ])
    location = dcc.Input(id={'type': 'output-location', 'name': 'form'},
                         type="text", style={'display': 'none'})
    return html.Div([title, textarea, location])


def create_form2():
    title = html.Div([
        html.H3("Form2")
    ])
    textarea = html.Div([
        html.P("Form2 text"),
        dcc.Textarea(
            id={'type': 'text-form', 'name': 'form2'},
            style={'width': '100%', 'height': 200},
        ),
        html.Button(
            'Submit', id={'type': 'form-button', 'name': 'form2'}, n_clicks=0)
    ])
    location = dcc.Input(id={'type': 'output-location', 'name': 'form'},
                         type="text", style={'display': 'none'})
    return html.Div([title, textarea, location])


@app.callback(
    Output({'type': 'output-location', 'name': 'form'}, 'value'),
    [
        Input({'type': 'form-button', 'name': ALL}, 'n_clicks')
    ],
    [
        State({'type': 'text-form', 'name': ALL}, 'value')
    ]
)
def create_form_callback(n_clicks, textarea_value):
    ctx = dash.callback_context

    if ctx.triggered:
        # Do something with input data...
        print(n_clicks, textarea_value)

        # Find what form initiated the callback
        clicked = json.loads(
            ctx.triggered[0]['prop_id'].split('.')[0]).get('name')
        # Return to URL (will be processed by display_page callback)
        return "/location/to/return/after/submit/{}".format(clicked)
    else:
        return dash.no_update


@app.callback(
    Output('url', 'pathname'),
    [
        Input({'type': 'output-location', 'name': ALL}, 'value'),
    ],
)
def update_location(values):
    ctx = dash.callback_context
    if ctx.triggered:
        return values[0]
    else:
        return dash.no_update


@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/page-1':
        return page1()
    elif pathname == '/page-2':
        return page2()
    elif pathname == '/form1':
        return create_form1()
    elif pathname == '/form2':
        return create_form2()
    else:
        return default(pathname)


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