A nonexistent object was used in an `Output` of a Dash callback - correct use of multi-Page App Validation

Hi @AnnMarieW and community,

I got something that partially works. My typical callback is like this:

@callback([Output('plotting_complete_message', 'children'),
           Output('plot_1', 'figure'),
           Output('plot_2', 'figure')],
          [Input('dataframe_1', 'data')])
  
def plot_generator_1(dataframe_1):
       # Check if input does not contain data and prevent app update.
       if dataframe_1 is None:
            raise PreventUpdate

       # Check if input contains data and trigger callback.
       elif dataframe_1 is not None:
            some code that returns plot_1 and plot_2
 
            plotting_complete_message = html.H3("Complete")

            return(plotting_complete_message, plot_1, plot_2)
       else:
            return(no_update, no_update, no_update)

I realised something after reading this post:https://community.plotly.com/t/a-nonexistent-object-was-used-in-an-output-of-a-dash-callback/60897/18

The callbacks linked to output errors, were all like in my toy example above. The “plotting_complete_message” was referenced in my data upload page layout, passing a “Complete” message to the page, signalling that plot could be viewed in the visualisation page. The plots are figures passed to and referenced on the visualisation page layout. In other words 1 callback had outputs referenced in 2 page layouts.

I therefore split my callback into a plot generation callback and a “listener” callback:

Plot generation callback

@callback([Output('plot_generator_1_status', 'data'),
           Output('plot_1', 'figure'),
           Output('plot_2', 'figure')],
          [Input('dataframe_1', 'data')])
  
def plot_generator_1(dataframe_1):
       # Check if input does not contain data and prevent app update.
       if dataframe_1 is None:
            raise PreventUpdate

       # Check if input contains data and trigger callback.
       elif dataframe_1 is not None:
            some code that returns plot_1 and plot_2
 
            plot_generator_1_status = "complete"

            return(plot_generator_1_status, plot_1, plot_2)
       else:
            return(no_update, no_update, no_update)

Listener callback

@callback([Output('plot_complete_message', 'children')],
          [Input('plot_generator_1_status', 'data')])
    def plot_generator_1_listener(plot_generator_1_status):
        # First condition if callback not completed.
        if plot_generator_1_status is None:
            # Exception raised in order to suppress error.
            raise PreventUpdate
        
        # If callback completed, generate message.
        elif plot_generator_1_status == 'complete':
            
            plot_complete_message = html.H3('Complete')

            return(plot_complete_message)
        
        else:
            return(no_update)

I now update my global store with dcc.Store(id = plot_generator_1_status). Rinse and repeat for all callbacks with probematic dual outputs.

App now behaves as follows:

Clicking betwen pages doesn’t raise errors.

Data uploads as normal with messages regarding initial pre-processing of data displayed on upload page. However, the “plot_complete_messages” for plots don’t display in the upload page and update errors return.

What works:

If I now navigate to the “visualisation” page, the app immediately starts updating and eventually displays my plots in each of the sub-tabs. It’s about as quick as my 1 page with tabs app. It seems clicking on the “visualisation” page kickstarts my plotting callbacks. In my one page with tabs approach, the plots will render in the “visualisation” tab whilst I’m still in the upload tab, monitoring status messages. This will not work, if I remove the callback splitting method.

Additionally, the output ID errors in the “visualisation” page now don’t appear whilst plots are rendered, so half the battle won! It’s still confusing as those ID’s are still not referenced in the “visualisation” page layout.

What doesn’t work:

1 - My plot completion messages don’t update upload page. This is still due to callback output ID’s for plots only being referenced in the “visualisation” page. I reverted to new approach for pages that already has validation baked in, as @AnnMarieW mentioned in previous post, but same behaviour. Did shorten my code though!

2 - Clicking to other pages and back re-starts the callback chain. I don’t know how to suppress this refresh behaviour. Passing
storage_type='session' to dcc.Store doesn’t work, because I have no plots in stores, only tables and string variables.

3 - If I click on another browser tab while plots are updating, then go back to app whilst it’s updating plots, I sometimes get get Callback failed: the server did not respond. errors.

There are additional layers of complexity here that are beyond my skill level with Dash. Unfortunately, trying to work up a MWE that is representative of my functionality and reproduces this behaviour has defeated me. I’m going to park this for now and revert to my old approach.

If anyone has a generic template MWE of a multi-page app with data upload and visualisation pages sending data to each other, I would love to see it.

Again, apologies for long post (I’ve been down the rabbit hole for weeks and need to tell someone haha). Perhaps this will be useful for people with similar issues.

@AnnMarieW thanks for your help!