Confused about callback triggers/callback order

Hi all,

I’m using the file picker tree in my application, as shown in this forum post: File Explorer Tree Generator for local files.

I’ve modified it so individual files at the end of the tree are buttons. I have two callbacks in my app right now:
1st checks which button is pressed and saves the clicked file-path into a dcc.Store.
2nd callback cleans and creates data from the selected file path, then puts that data in a separate dcc.Store.

It seems like my 2nd callback isn’t triggering. My buttons do link me to another page to display results, but the “create_wafer_data” is never triggered, as I don’t see the “CREATING NEW DATA” print statement. I believe the first callback should be properly storing the file path, as previously, I had the button send me to the “results” page, with a simple dmc.Text that displayed which filepath was selected.

Does anybody have pointers as to what could be causing this?

Here’s some short code snippets of my callbacks:
1st Callback:

# Callback to store the selected file path
@callback(
    Output('selected-file-path', 'data'),
    Input({'type': 'file-item', 'index': ALL}, 'n_clicks'),
    State({'type': 'file-item', 'index': ALL}, 'id'),
    prevent_initial_call=True  # Prevent the callback from being called on initial load
)
def store_selected_file(n_clicks, ids):
    if not n_clicks or all(click is None for click in n_clicks):
        return dash.no_update

    # Find the index of the most recently clicked button
    clicked_index = max(range(len(n_clicks)), key=lambda i: n_clicks[i] if n_clicks[i] is not None else -1)
    
    # Ensure that the button was actually clicked (n_clicks > 0)
    if n_clicks[clicked_index] > 0:
        print("STORING NEW DATA")
        selected_file_path = ids[clicked_index]['index']
        return selected_file_path

    return dash.no_update

2nd Callback:

@callback(
    Output("wafer-data", "data"),
    Input("selected-file-path", "data"),
    prevent_initial_call=True
)
def create_wafer_data(selected_csv_path):
    print("CREATING NEW DATA")

dcc.Store within my app.py

app.layout = html.Div(
    [
        create_appshell(dash.page_registry.values()), 
        dcc.Store(id="selected-file-path", data="", storage_type="memory", clear_data=True),
        dcc.Store(id="wafer-data", data="", storage_type="memory", clear_data=True)
    ]
)

Thank you for taking the time to look at my post :slight_smile:

My thought process is as follows:
If callback #1 is triggered (e.g., I see “STORING NEW DATA”), I figured that this would change the Input of callback #2 and trigger callback #2, and should show me “CREATING NEW DATA”.

This line in the documentation of Basic Callbacks leads me to believe this, however my application currently isn’t acting this way:

  1. The “inputs” and “outputs” of our application are described as the arguments of the @callback decorator.
    a. By writing this decorator, we’re telling Dash to call this function for us whenever the value of the “input” component (the text box) changes in order to update the children of the “output” component on the page (the HTML div).

Hello @liuto ,

Yes, if you click on a button and see “STORING NEW DATA”, it should trigger “CREATING NEW DATA”.

I don’t see anything wrong in this code. Can you give a simplified, runnable app ? Reducing it to a bare minimum will also help you to debug it.

You can also try to replace the buttons / link provided by the File Explorer Tree Generator by regular buttons : does this work ? If yes, the problem lies between the File Explorer code and what you expect to do with.

Hi @spriteware,

Thanks for taking time to respond. The complexity of my own application exceeds my own capabilities to be honest. I’m having a hard time reducing to a minimum viable example.

I’ve deployed this application now, but the weird thing is that this callback works correctly on the server computer. However any computer outside of the server PC that hosts the application, it correctly states “STORING NEW DATA” (I’ve printed the file path as well), but for some reason despite returning selected_file_path, this update doesn’t trigger the 2nd callback.

In short, the 2nd callback successfully triggers on the server PC, but not elsewhere . I’m at a loss at what could be causing this.

I think I shot myself in the foot in designing this app. Pressing the button that “stores” the selected file path sends the user to another page. This page is triggering before the second callback can process the given file path.

So now that’s out of the way, my next question is: How do I implement some sort of 2 second delay when clicking a button?

What is the difference in the way that you launch the app on the server computer, vs. the other computers ?

Have you tried to take a look at the Developer console in the Web Browser ? It might indicate an error that do not pop-up in Dash.

Also another idea: You can inspect if the callback is triggered by Dash. A callback triggered = a network request (HTTP request) made from you browser to the server. Here is the procedure:

  1. Open network tab in the Developer Tools of your web browser.
  2. In the network tab, restrict the list to XHR / HTTP requests.
  3. Click on the button/link that is supposed to trigger callback 1 and callback 2.
  4. Do you see an HTTP request that corresponds to the callback n°2 ? If yes : what’s in the server response ? Is it empty ? What is the HTTP status, is it 200 ?

Here’s an example :


As I said, reducing to a minimum viable example is a good way to debug something that doesn’t work and you have no clues why. If you don’t know where to start, that’s simple :

  1. Pick something that maybe is not related, or not necessary for callback 1 and 2. Comment it. (It can be a button, a callback, etc…)
  2. Do you still have this weird behavior ?
  3. If yes, continue, remove something else. If no : you found something interesting, you now have a clue .
  4. Repeat this process until you either have a clue or either have an app that is the bare minimum to share here.
1 Like

OK. So maybe the whole thing I explained right before is overkill.

Indeed it is just a timing problem. If you develop on server you might have parallel callback execution, which might not be the case locally. Depends on how you launch your dash app?

I suggest you change the way you approach the problem : why this button should trigger both a page change and a save ? Maybe it should first save and second change the page.

1 Like

Apologies for not explaining myself better earlier. The button, since it represents the contents of a file, if the user clicks it they want to view it, so it changes the page to show them the results as well.

Here’s the button being generated by the tree:

def make_file(file_name, file_path):
        return dcc.Link(
            dmc.Button(
                    [DashIconify(icon="akar-icons:file"), " ", file_name],
                    style={"paddingTop": "5px"},
                    id={"type": "file-item", "index": file_path, "page": "fpmselect"},
                    variant="subtle",
                    size="xs",
                    n_clicks=0
                ),
                href='/fpmresults'
        )

When removing the “dcc.Link” portion, I notice my callbacks work perfectly fine. It’s the fact that clicking the button sends the user immediately to another page.

I suggest you change the way you approach the problem : why this button should trigger both a page change and a save ? Maybe it should first save and second change the page.

Thank you for this pointer, and I agree with you. I’m going to have to do some thinking as to what a good solution for this would. I like having the button itself change the page too, because it feels very uniform on the user experience side. Simply click the file you want to view and the results will show. But obviously this doesn’t work.