Use URL query parameter when rendering initial layout

I have a single page Dash app that includes a dash_table of users, user-select-table. When you click on a cell in that table, the user represented by that row is “selected” and other components on the page update to show information about that user. I want to make it easy to jump to the selection of a specific user, so that if you go to myapp[.]com/?user_id=3, the app loads with User ID 3 already selected. Furthermore, when you click on a cell in user-select-table, the app URL updates to include ?user_id=<selected user ID>. The idea is that someone looking at user ID 5 can easily copy/paste the URL from their URL bar and share it with someone else, and when that someone else follows the link, the app will open with user ID 5 selected and all of the components on the page are displaying that user’s info.

Essentially I want to use the value of a URL query parameter when rendering the initial layout, but after that I want the URL query parameter to be exclusively an Output of my callback, and never an Input.

The docs here show how to do this if using dash pages, but I want to keep my app single-page.

The code for an example app, modeled after the example in the docs, is below. When I go to the app’s URL without any URL query parameters (e.g. localhost:8050/), the user-id-message reads “You have selected user: 1”, as expected. When I click on a cell in the table (let’s say User ID 3), the URL updates as expected, to localhost:8050/?user_id=3. But when I open a new browser window and navigate to localhost:8050/?user_id=3, User ID 1 is selected. How can I get my single-page app to use the URL query parameter as it’s rendering its initial layout?

import pandas as pd

from dash import Dash, dcc, dash_table, Input, Output, callback, html, no_update

# Example data
df = pd.DataFrame({
    'UserID': [1, 2, 3, 4, 5, 6],
    'SessionID': [1001, 1002, 1003, 1004, 1005, 1006],
    'Group': ["A", "A", "A", "B", "B", "B"],
    'Value': [25.94, 73.79, 22.76, 32.69, 15.82, 8.11]
})
df["id"] = df["UserID"] # for dash_table's row_id

app = Dash(__name__)

def layout(**kwargs):
    user_id = kwargs.get("user_id")
    if user_id is None or user_id not in df["id"].values:
        user_id = df["id"].values[0]
    selected_user_row_ix = df.index[df['UserID'] == user_id].tolist()[0]
    user_table = dash_table.DataTable(
        id="user-select-table",
        columns=[
            {"name": "User ID", "id": "UserID"},
            {"name": "Session ID", "id": "SessionID"},
            {"name": "Group", "id": "Group"},
            {"name": "Value", "id": "Value"},
        ],
        fixed_columns={"headers": True, "data": 0},
        data=df.to_dict("records"),
        active_cell = {
            "row": selected_user_row_ix,
            "column": 0
        },
        is_focused = True
    )
    return(html.Div([
        dcc.Location(id="url", refresh=False),
        html.H1('Hello Dash'),
        html.Div(f'You have selected user: {user_id}.', id="user-id-message"),
        user_table
    ]))


app.layout = layout

@callback(
    Output("url", "search"),
    Output("user-id-message", "children"),
    Input("user-select-table", "active_cell"),
    prevent_initial_call=True
)
def update_selected_user(active_cell):
    if not active_cell:
        return(no_update, "Select a user")
    selected_user_id = active_cell.get("row_id")
    new_msg = f'You have selected user: {selected_user_id}.'
    return(f"?user_id={selected_user_id}", new_msg)

if __name__ == '__main__':
    app.run()

Hi @SamuelCatams

You can use Pages, even if it’s a single page app.

You don’t need to use the pages folder - set: pages_folder="":

app = Dash(__name__, use_pages=True, pages_folder="")

You can register the layout as a page like this, then display it in dash.page_container:


dash.register_page("home", layout=layout, path="/")

app.layout = html.Div(dash.page_container)

Here’s a complete example. Note that the the user_id from the URL will be a string, so it either needs to be converted to a number – or it may be easier to make the UserID in the df a string.


import dash
import pandas as pd

from dash import Dash, dcc, dash_table, Input, Output, callback, html, no_update

# Example data
df = pd.DataFrame({
    'UserID': [1, 2, 3, 4, 5, 6],
    'SessionID': [1001, 1002, 1003, 1004, 1005, 1006],
    'Group': ["A", "A", "A", "B", "B", "B"],
    'Value': [25.94, 73.79, 22.76, 32.69, 15.82, 8.11]
})
df["id"] = df["UserID"] # for dash_table's row_id

app = Dash(__name__, use_pages=True, pages_folder='')

def layout(**kwargs):
    user_id = kwargs.get("user_id")
    if user_id:
        try:
            user_id= int(user_id)
        except ValueError:
            print(f"{user_id} must be an integer")
    if user_id is None or user_id not in df["id"].values:
        user_id = df["id"].values[0]
    selected_user_row_ix = df.index[df['UserID'] == user_id].tolist()[0]
    user_table = dash_table.DataTable(
        id="user-select-table",
        columns=[
            {"name": "User ID", "id": "UserID"},
            {"name": "Session ID", "id": "SessionID"},
            {"name": "Group", "id": "Group"},
            {"name": "Value", "id": "Value"},
        ],
        fixed_columns={"headers": True, "data": 0},
        data=df.to_dict("records"),
        active_cell = {
            "row": selected_user_row_ix,
            "column": 0
        },
        is_focused = True
    )
    return(html.Div([
        dcc.Location(id="url", refresh=False),
        html.H1('Hello Dash'),
        html.Div(f'You have selected user: {user_id}.', id="user-id-message"),
        user_table
    ]))

dash.register_page("home", layout=layout, path="/")

app.layout = html.Div(dash.page_container)

@callback(
    Output("url", "search"),
    Output("user-id-message", "children"),
    Input("user-select-table", "active_cell"),
    prevent_initial_call=True
)
def update_selected_user(active_cell):
    if not active_cell:
        return(no_update, "Select a user")
    selected_user_id = active_cell.get("row_id")
    new_msg = f'You have selected user: {selected_user_id}.'
    return(f"?user_id={selected_user_id}", new_msg)

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



1 Like