2

I'm trying to generate a table from a callback and although the table is in fact getting generated, with it I get a warning:

Callback error updating priceTable.data, priceTable.columns

dash.exceptions.InvalidCallbackReturnValue: The callback ..priceTable.data...priceTable.columns.. is a multi-output. Expected the output type to be a list or tuple but got: None.

I know my problem is in how I'm laying out the callback, but I'm not sure what the correct formatting should be. I found this answer online which helped a lot since I had 'return dt.DataTable(data = data, columns=columns)' before it.

Code layout:

import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_table as dt
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')

app = dash.Dash(__name__)

states = df.State.unique().tolist()
numSP = df['Number of Solar Plants'].unique().tolist()

app.layout = html.Div([
    dcc.Dropdown(
            id='filter_dropdown',
            options=[{'label':st, 'value':st} for st in states],
            value = states[0]
            ),
    html.Br(),
    dcc.Dropdown(
            id='filter2',
            options=[{'label':np, 'value':np} for np in numSP]
            #value = numSP[0]
            ),
    html.Br(),
    html.Button('Submit', id='submit-button-state', n_clicks=0),
    html.Br(),
    html.Div(id='output'),
    html.Div(
        #id ='priceTable'
        dt.DataTable(id='priceTable')
        )
])

Callback section code:

@app.callback(
    [Output('priceTable', 'data'),Output('priceTable', 'columns')],
    [Input('submit-button-state', 'n_clicks')],
    [State('filter_dropdown', 'value')],
    [State('filter2', 'value')],
    [State('submit-button-state', 'n_clicks')],
)
def update_table(n_clicks, filter_dropdown, filter2, table):
    if n_clicks:
        if filter_dropdown is None and filter2 is None:
            raise PreventUpdate
        if filter_dropdown is not None and filter2 is not None:
            table = df[df.State == filter_dropdown]
            table = table[table['Number of Solar Plants'] == filter2]
        if filter_dropdown is None and filter2 is not None:
            table = df[df['Number of Solar Plants'] == filter2]
        if filter_dropdown is not None and filter2 is None:
            table = df[df.State == filter_dropdown]
        columns = [{"name": i, "id": i,} for i in (table.columns)]    
        data = table.to_dict('records')
        return data, columns

Things I've tried:

  • I tried listing my output differently: [Output('priceTable', 'data')],[Output('priceTable', 'columns')], or [Output('priceTable', 'data'),Output('priceTable', 'columns'),],
  • Assigning data and column to a variable that get's returned:
priceTable = (data, columns)
return priceTable 

Thank you!

1 Answer 1

2

The reason it is not working is because you're not handling the case in your callback where the button has not been clicked. Even if the button hasn't been clicked your callback still needs to return a tuple (of your data and columns).

That is what the error is saying here:

Expected the output type to be a list or tuple but got: None.

You don't return anything (None) when n_clicks is 0 (false).

So what you need to do is return the data and columns when n_clicks is 0. The only difference in approach from what you have is that you don't apply any filters to the data if n_clicks is 0.

Full example:

import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_table as dt
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import pandas as pd

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/solar.csv")

app = dash.Dash(__name__)

states = df.State.unique().tolist()
numSP = df["Number of Solar Plants"].unique().tolist()

app.layout = html.Div(
    [
        dcc.Dropdown(
            id="filter_dropdown",
            options=[{"label": st, "value": st} for st in states],
            value=states[0],
        ),
        html.Br(),
        dcc.Dropdown(
            id="filter2",
            options=[{"label": np, "value": np} for np in numSP]
            # value = numSP[0]
        ),
        html.Br(),
        html.Button("Submit", id="submit-button-state", n_clicks=0),
        html.Br(),
        html.Div(id="output"),
        html.Div(
            # id ='priceTable'
            dt.DataTable(id="priceTable")
        ),
    ]
)


def get_table_data(data):
    columns = [
        {
            "name": i,
            "id": i,
        }
        for i in (data.columns)
    ]
    data = data.to_dict("records")
    return (data, columns)


@app.callback(
    [Output("priceTable", "data"), Output("priceTable", "columns")],
    [Input("submit-button-state", "n_clicks")],
    [State("filter_dropdown", "value")],
    [State("filter2", "value")],
    [State("submit-button-state", "n_clicks")],
)
def update_table(n_clicks, filter_dropdown, filter2, table):
    if n_clicks:
        if filter_dropdown is None and filter2 is None:
            raise PreventUpdate
        if filter_dropdown is not None and filter2 is not None:
            table = df[df.State == filter_dropdown]
            table = table[table["Number of Solar Plants"] == filter2]
        if filter_dropdown is None and filter2 is not None:
            table = df[df["Number of Solar Plants"] == filter2]
        if filter_dropdown is not None and filter2 is None:
            table = df[df.State == filter_dropdown]

        (data, columns) = get_table_data(table)
        print("columns", columns)
        print("data", columns)

        return data, columns

    return get_table_data(df)


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

In the example above I've also put your code for retrieving data and columns from a dataframe in its own function get_table_data. For making sure a tuple of your data is always returned I've used a guard clause.

Sign up to request clarification or add additional context in comments.

1 Comment

Bas, thank you so much! This was the perfect solution I did have to add an else and 'raise PreventUpdate()' clause at the end of the if statement. Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.