Repository: plotly/dash

Issue:
[BUG] Order of Inputs, Outputs and States in long_callbacks causing problems
An issue I had was [described as a potential bug on community.plotly](https://community.plotly.com/t/issues-switching-from-callback-to-long-callback/66882/4) and it was recommended I share here.

The following code runs properly:

```
import webbrowser
import diskcache

from threading import Timer
from dash import Dash, dcc, html, Input, Output, State
from dash.long_callback import DiskcacheLongCallbackManager

cache=diskcache.Cache("./cache")
long_callback_manager=DiskcacheLongCallbackManager(cache)

list_of_tables=[['Generic Table',['Table Columns']]]

app = Dash(__name__)

app.layout = html.Div([
    dcc.Checklist([table[0] for table in list_of_tables],id='table checklist'),
    html.H3(children='Run simulation: '),
    html.Button(id='run simulation button',n_clicks=0,children='Run'),
    html.Div(id='my-output'),
])

@app.callback(
    Output('my-output', 'children'),
    State('table checklist','value'),
    Input('run simulation button', 'n_clicks'),
    prevent_initial_call=True
    )
def test(tables_to_compute,n_clicks):

    if 'Generic Table' in tables_to_compute:
        string='Generic table is checked'
    else:
        string='Generic table is not checked'

    return string

if __name__ == '__main__':
    Timer(1, webbrowser.open('http://127.0.0.1:8054/')).start();
    app.run_server(debug=True,use_reloader=False,port=8054)
```
but when I change the callback decorator to
```
@app.long_callback(
    Output('my-output', 'children'),
    State('table checklist','value'),
    Input('run simulation button', 'n_clicks'),
    manager=long_callback_manager,
    prevent_initial_call=True
    )
```
an error occurs, telling me "TypeError: argument of type ‘int’ is not iterable". Here is the reply from community.plotly:

"Thanks for the reproducible example!

My suspicion before running was that it was mixing the order between Input and State and assigning the click count to tables_to_compute (therefore your error). I can confirm that this is exactly what happened and the error disappears if you switch the order in the decorator and in the function signature.

I think you can consider this a bug, although it is a good practice to have the Input before State in callbacks… I would recommend you to open an issue on GH to report it just in case."


Discussion:
Thanks @nbolohan! Feel free to include a link to the community post so the whole discussion stays together.

Is this still a problem with the updates in v2.6? (`@app.callback(background=True)` is the new syntax, `@app.long_callback` is deprecated, and the architecture behind it has changed substantially)
Would the appropriate changes be as follows? If so, the same error is produced if Input is below State (I can confirm I am using 2.6.0 as well)
```
@app.callback(
    Output('my-output', 'children'),
    State('table checklist','value'),
    Input('run simulation button', 'n_clicks'),
    manager=long_callback_manager,
    prevent_initial_call=True,
    background=True
    )
```
Yep that's it - thanks, so this bug survived the refactor, good to know!
The background callback call with the wrong arguments.

https://github.com/plotly/dash/blob/b569dfe969edccfed621c93b5ec9d21c3ba09698/dash/_callback.py#L362-L378

`args` here should the same as the key:

`func_args if func_args else func_kwargs`


@T4rk1n but shouldn't background and foreground callbacks support exactly the same args? Putting `State` before `Input` works fine for regular callbacks but fails for background.
> but shouldn't background and foreground callbacks support exactly the same args?

Yes but it's giving the raw `args` instead of the `func_args` which are ordered.

Resolving patch:
diff --git a/dash/_callback.py b/dash/_callback.py
--- a/dash/_callback.py
+++ b/dash/_callback.py
@@ -362,7 +362,7 @@ def add_context(*args, **kwargs):
                     job = callback_manager.call_job_fn(
                         cache_key,
                         job_fn,
-                        args,
+                        func_args if func_args else func_kwargs,
                         AttributeDict(
                             args_grouping=callback_ctx.args_grouping,
                             using_args_grouping=callback_ctx.using_args_grouping,


Test patch:
diff --git a/tests/integration/long_callback/app_unordered.py b/tests/integration/long_callback/app_unordered.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/long_callback/app_unordered.py
@@ -0,0 +1,31 @@
+from dash import Dash, Input, Output, dcc, State, html, callback
+
+from tests.integration.long_callback.utils import get_long_callback_manager
+
+long_callback_manager = get_long_callback_manager()
+handle = long_callback_manager.handle
+
+app = Dash(__name__, long_callback_manager=long_callback_manager)
+
+app.layout = html.Div(
+    [
+        html.Div(id="output"),
+        html.Button("click", id="click"),
+        dcc.Store(data="stored", id="stored"),
+    ]
+)
+
+
+@callback(
+    Output("output", "children"),
+    State("stored", "data"),
+    Input("click", "n_clicks"),
+    background=True,
+    prevent_initial_call=True,
+)
+def update_output(stored, n_clicks):
+    return stored
+
+
+if __name__ == "__main__":
+    app.run_server(debug=True)
diff --git a/tests/integration/long_callback/test_basic_long_callback.py b/tests/integration/long_callback/test_basic_long_callback.py
--- a/tests/integration/long_callback/test_basic_long_callback.py
+++ b/tests/integration/long_callback/test_basic_long_callback.py
@@ -523,3 +523,11 @@ def test_lcbc012_long_callback_ctx(dash_duo, manager):
         output = json.loads(dash_duo.find_element("#result").text)
 
         assert output["triggered"]["index"] == 0
+
+
+def test_lcbc013_unordered_state_input(dash_duo, manager):
+    with setup_long_callback_app(manager, "app_unordered") as app:
+        dash_duo.start_server(app)
+        dash_duo.find_element("#click").click()
+
+        dash_duo.wait_for_text_to_equal("#output", "stored")