The approach below works for dash version 1.19.0 up to the current version 1.20.0 (and probably onwards).
[1.19.0] - 2021-01-19: Adds support for callbacks which have overlapping inputs and outputs. Combined with dash.callback_context this addresses many use cases which require circular callbacks.
https://github.com/plotly/dash/blob/dev/CHANGELOG.md#added-1
As you've already said you can use the n_clicks property
of the buttons to get the number of times each button has been clicked.
n_clicks can also be set using a regular Output.
...Or do I need to store variables in storage and then update them everytime a button is clicked?
n_clicks and dash.callback_context track this for you so I don't think there is a need to track it yourself also.
In addition to event properties like n_clicks that change whenever an event happens (in this case a click), there is a global variable dash.callback_context, available only inside a callback.
https://dash.plotly.com/advanced-callbacks
Example of how you could go about it (Adjusted version of the advanced callback example in the documentation linked above):
import json
import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Button("Button A", id="btn-a"),
html.Button("Button B", id="btn-b"),
html.Div(id="container"),
]
)
@app.callback(
Output("container", "children"),
Output("btn-a", "n_clicks"),
Output("btn-b", "n_clicks"),
Input("btn-a", "n_clicks"),
Input("btn-b", "n_clicks"),
prevent_initial_call=True,
)
def display(n_clicks_a, n_clicks_b):
ctx = dash.callback_context
ctx_msg = json.dumps(
{"states": ctx.states, "triggered": ctx.triggered, "inputs": ctx.inputs},
indent=2,
)
if n_clicks_a is not None and n_clicks_b is not None and n_clicks_b > n_clicks_a:
return html.Pre(ctx_msg), 0, 0
return html.Pre(ctx_msg), n_clicks_a, n_clicks_b
if __name__ == "__main__":
app.run_server(debug=True)
In addition to using Input you could also use State to get n_clicks if you
don't want the callback to trigger on a change of n_clicks, but you do want access to its value.