<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Archives des tutorial | dash-resources.com</title>
	<atom:link href="https://dash-resources.com/tag/tutorial/feed/" rel="self" type="application/rss+xml" />
	<link>https://dash-resources.com/tag/tutorial/</link>
	<description>Learn to build interactive web applications with Python and Dash plotly</description>
	<lastBuildDate>Tue, 17 Feb 2026 23:07:03 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://dash-resources.com/wp-content/uploads/2024/12/cropped-dash-logo-favicon-512-32x32.png</url>
	<title>Archives des tutorial | dash-resources.com</title>
	<link>https://dash-resources.com/tag/tutorial/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How to secure a multi-page Dash app with dash-auth</title>
		<link>https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 28 Aug 2025 08:44:53 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[dash-auth]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=926</guid>

					<description><![CDATA[<p>In this tutorial, we will see how to secure your Dash multi-page app using the basic authentication mechanism in dash-auth. It’s the natural continuation of [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/">How to secure a multi-page Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we will see how to secure your <a href="https://dash.plotly.com/urls">Dash multi-page</a> app using the basic authentication mechanism in <code>dash-auth</code>.</p>



<p>It’s the natural continuation of the previous tutorial: <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">How to secure a Dash app with dash-auth</a>, where you can learn how to use <code>dash-auth</code>, connect to a database, and display the username.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#dash-auth">Dash-auth</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#app-py">app.py</a>
          </li>
          <li>
            <a href="#pages-public-py">pages/public.py</a>
          </li>
          <li class="last">
            <a href="#pages-private-py">pages/private.py</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#result">Result</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let’s go! </p>



<h2 id='dash-auth'  id="boomdevs_1" class="wp-block-heading" >Dash-auth</h2>



<p>If you haven’t yet, install <code>dash</code> and <code>dash-auth</code>:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">pip install dash dash-auth
</code></pre>



<p>Then, create three files and one folder:</p>



<ul class="wp-block-list">
<li><code>app.py</code>: the main app file</li>



<li><code>pages/public.py</code>: the publicly accessible page at URL &#8220;/&#8221;</li>



<li><code>pages/private.py</code>: the private page at URL &#8220;/private&#8221;</li>
</ul>



<h3 id='app-py'  id="boomdevs_2" class="wp-block-heading" >app.py</h3>



<p>This file will be very similar to what we had in the previous tutorial. The list of users is hardcoded at the beginning of the file:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html, dcc, page_container
import dash_auth

# Note: better store the list in a secrets file or in a database.
USERS = {
    "alice": "secret123",
    "bob": "pa$$w0rd"
}

# Create the Dash app
app = Dash(
    __name__,
    use_pages=True,
    suppress_callback_exceptions=True
)

# Add authentication
auth = dash_auth.BasicAuth(
    app,
    username_password_list=USERS,
    secret_key="something_like_nUGz8DZvb...",
    # Public routes are not protected by authentication.
    # All others are protected by default
    public_routes=["/"]
)

# Define the layout
app.layout = html.Div([
    html.H1("My app"),
    html.P(dcc.Link("Public page", href="/")),
    html.P(dcc.Link("Private page", href="/private")),
    html.Hr(),
    page_container
])

if __name__ == '__main__':
    app.run(debug=True)

</code></pre>



<p>There are two key differences:</p>



<ul class="wp-block-list">
<li>We use pages for the Dash app (<code>use_pages=True</code> and <code>suppress_callback_exceptions=True</code>)</li>



<li>We define the public pages in Dash-auth using <code>public_routes</code>.</li>
</ul>



<p>Then, the layout shows two links to the two pages and the page content (using <code>page_container</code>).</p>



<h3 id='pages-public-py'  id="boomdevs_3" class="wp-block-heading" >pages/public.py</h3>



<p>The public page will contain a button that increments itself when clicked:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import html, dcc, register_page, callback, Output, Input
from dash_auth import public_callback

# The public page is the home
register_page(
    __name__,
    "/",
)

# The page layout
def layout():
    return html.Div([
        html.P("This is a public page."),
        html.Button("Click me (0)", id="button", n_clicks=0)
    ])

# A simple public callback
@public_callback(
    Output("button", "children"),
    Input("button", "n_clicks"),
)
def update_button(n_clicks):
    return f"Click me ({n_clicks})"

</code></pre>



<p>The only key change here is the use of <code>public_callback</code> (from <code>dash-auth</code>) instead of <code>callback</code>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" width="1024" height="508" src="https://dash-resources.com/wp-content/uploads/2025/08/image-3-1024x508.png" alt="" class="wp-image-929" style="width:553px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-3-1024x508.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-3-300x149.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-3-768x381.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-3.png 1168w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">The public, unprotected page</figcaption></figure>
</div>


<p class="callout">By default, all callbacks use the same URL endpoint: <code>/dash-update-component</code>. This is why we need to specify that this callback is public, while others are protected by default as soon as we add <code>dash-auth</code> to the app.<br><br>Under the hood, the callback ID is simply added to a list of whitelisted callbacks in the Flask server’s config.</p>



<h3 id='pages-private-py'  id="boomdevs_4" class="wp-block-heading" >pages/private.py</h3>



<p>The private page will only show the username using a layout function:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import html, register_page
import flask

register_page(
    __name__,
    "/private",
)

def layout():
    username = flask.session.get("user").get("email")

    return html.Div([
        html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />"),
        html.P(f"You are logged in as {username}.")
    ])
</code></pre>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" width="1024" height="530" src="https://dash-resources.com/wp-content/uploads/2025/08/image-4-1024x530.png" alt="" class="wp-image-930" style="width:565px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-4-1024x530.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-4-300x155.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-4-768x398.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-4.png 1136w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">The private page, protected by HTTP basic authentification.</figcaption></figure>
</div>


<p><code>dash-auth</code> saves the current username in a flask cookie, so we can get easily get it in the layout function, or within a callback.</p>



<h2 id='result'  id="boomdevs_5" class="wp-block-heading" >Result</h2>



<p>Now let’s run this app!<br>Here is a little video of what it looks like:</p>



<figure class="wp-block-video"><video height="852" style="aspect-ratio: 1386 / 852;" width="1386" controls src="https://dash-resources.com/wp-content/uploads/2025/08/dash-auth-pages.mp4"></video></figure>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;Note that once we’re logged in, we can’t log out manually. The browser must be closed instead.</p>



<h2 id='conclusion'  id="boomdevs_6" class="wp-block-heading" >Conclusion</h2>



<p>I hope this tutorial was instructive! I found it interesting that <code>dash-auth</code> evolved to secure multi-page apps as well, which I didn’t know until writing these two tutorials <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>As with the <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">previous tutorial</a>, the conclusion is similar: <code>dash-auth</code> has its drawbacks, but it’s a <strong>fairly easy solution</strong> to add an authentication layer to a dashboard or a data app. However, I would <strong>not recommend</strong> using it for production apps that have very sensitive data.</p>



<p>If you need proper login/logout behavior (e.g., session expiration, multiple users switching), you’ll need a more advanced system such as:</p>



<ul class="wp-block-list">
<li><code>flask_login</code></li>



<li>OAuth (Google, GitHub…)</li>



<li>Enterprise identity providers (Okta, Auth0, Azure AD…)</li>



<li><a href="https://plotly.com/dash/authentication/">Dash-Enterprise</a> (handles LDAP, SAML, OIDC)</li>
</ul>



<p>Happy Dash coding!</p>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/">How to secure a multi-page Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/08/dash-auth-pages.mp4" length="363032" type="video/mp4" />

			</item>
		<item>
		<title>How to secure a Dash app with dash-auth</title>
		<link>https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 28 Aug 2025 08:37:01 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[dash-auth]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=918</guid>

					<description><![CDATA[<p>In this tutorial, we’ll see how you can protect your Dash app with the package dash-auth. We’ll cover how to set up basic authentication, how [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">How to secure a Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we’ll see how you can protect your Dash app with the package <code>dash-auth</code>.</p>



<p>We’ll cover how to set up basic authentication, how to define a custom authentication function, and even how to connect it to a database.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#basic-dash-auth">Basic Dash auth</a>
      </li>
      <li>
        <a href="#retrieve-the-user">Retrieve the user</a>
      </li>
      <li>
        <a href="#authentication-function">Authentication function</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#comparing-hashed-passwords">Comparing hashed passwords</a>
          </li>
          <li class="last">
            <a href="#use-a-database">Use a database</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#why-you-can-t-logout">Why you can’t logout</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let’s go! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='basic-dash-auth'  id="boomdevs_1" class="wp-block-heading" >Basic Dash auth</h2>



<p>First, install Dash and Dash Auth:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">pip install dash dash-auth</code></pre>



<p>Next, define a list of users and their associated passwords:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html
import dash_auth

# <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Defining passwords directly in code is a bad practice.
# Better: store them in a secrets file or a database.
USERS = {
    "alice": "secret123",
    "bob": "pa$$w0rd"
}

# Create the Dash app
app = Dash(__name__)

# Add authentication
auth = dash_auth.BasicAuth(
    app,
    username_password_list=USERS,
    # Set the secret key to something random.
    secret_key="something_like_nUGz8DZvb..."
)

# Define the layout
app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />")
])

if __name__ == '__main__':
    app.run(debug=True)
</code></pre>



<p>The secret key must be unique and… secret <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" />.<br>To generate one, run this in your terminal:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">python -c "import secrets; print(secrets.token_urlsafe(32))"
</code></pre>



<p class="callout"><strong>Note:</strong> the <code>secret_key</code> isn’t mandatory for <code>dash-auth</code> since it doesn’t use cookies. However, adding it removes the warning: “<em>WARNING:root:Session is not available. Have you set a secret key?</em>”.</p>



<p>Then, just run your app with <code>python app.py</code>. You should get this result:</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1824" height="1144" src="https://dash-resources.com/wp-content/uploads/2025/08/image.png" alt="" class="wp-image-919" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image.png 1824w, https://dash-resources.com/wp-content/uploads/2025/08/image-300x188.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-1024x642.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-768x482.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-1536x963.png 1536w" sizes="(max-width: 1824px) 100vw, 1824px" /></figure>



<p>And after log-in:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1630" height="1058" src="https://dash-resources.com/wp-content/uploads/2025/08/image-1.png" alt="" class="wp-image-920" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-1.png 1630w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-300x195.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-1024x665.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-768x498.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-1536x997.png 1536w" sizes="auto, (max-width: 1630px) 100vw, 1630px" /></figure>



<p>That’s it! You get a protected page. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f929.png" alt="🤩" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>There are some <strong>limitations</strong>:</p>



<ul class="wp-block-list">
<li>You cannot customize the login/password popup window, it’s natively handled by the browser.</li>



<li>Users cannot logout: the session is active until they close their browser.</li>



<li>The list of users is fixed: you will need to reload the app to add or remove a user or update a password.</li>
</ul>



<h2 id='retrieve-the-user'  id="boomdevs_2" class="wp-block-heading" >Retrieve the user</h2>



<p>The user information is stored in the <code>flask.session</code> object. It’s therefore easy to retrieve it in a callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># ...

# Define the layout
app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />"),
    html.P(id="user_info")  # added this
])

# Define the callback
@app.callback(
    Output("user_info", "children"),
    Input("user_info", "id"),  # dummy trigger
)
def update_user_info(_):
    """ Display the username of the logged in user. """
    username = flask.session.get("user").get("email")
    return f"You are logged in as {username}."

if __name__ == '__main__':
    app.run(debug=True)
</code></pre>



<p>Let’s explain this code:</p>



<ul class="wp-block-list">
<li>I added a new paragraph #user_info</li>



<li>A new callback is triggered at initialization time. It displays the username.</li>
</ul>



<p>Now let’s see the result:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1630" height="1058" src="https://dash-resources.com/wp-content/uploads/2025/08/image-2.png" alt="" class="wp-image-923" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-2.png 1630w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-300x195.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-1024x665.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-768x498.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-1536x997.png 1536w" sizes="auto, (max-width: 1630px) 100vw, 1630px" /></figure>



<h2 id='authentication-function'  id="boomdevs_3" class="wp-block-heading" >Authentication function</h2>



<p>It’s possible to use an arbitrary python function to handle authentication. This is useful to:</p>



<ul class="wp-block-list">
<li>store and compare hashed passwords instead of plain passwords;</li>



<li>retrieve user information and passwords from a database.</li>
</ul>



<h3 id='comparing-hashed-passwords'  id="boomdevs_4" class="wp-block-heading" >Comparing hashed passwords</h3>



<p>Storing passwords in clear in the code is a pretty bad practice for security and confidentiality reasons. Instead, a better practice is to store hashed passwords instead of the clear version. If the password leaks, the original password can&#8217;t be found!</p>



<p>You can hash a password using the <code>generate_password_hash</code> from <code>werkzeug.security</code> package. For instance:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from werkzeug.security import generate_password_hash
print(generate_password_hash("secret123"))
# scrypt:32768:8:1$PICpPDH9JdIz75DT$d5f81a560015a407e51989f03d046816954ba2b8d5ee520b999f492352b9e8a39084210ea2fe3bfdcdc2a94047a5397e04bbf3663b505967d3d1ea91a95101d7'
</code></pre>



<p>Then, we can compare the two password hashes with the <code>check_password_hash</code> function:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">
from werkzeug.security import check_password_hash

def auth_func(username, password):
    """ Authenticate the user using hashed passwords. """

    # Check the user exists and the password is correct
    if username in USERS and check_password_hash(USERS[username], password):
        return True
    return False
</code></pre>



<p>That&#8217;s it. We just check the user exists, and compare its hash.</p>



<p>Let&#8217;s put this all together and replace the <code>USERS</code> list with the <code>auth_func</code> parameter:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html
from dash_auth import BasicAuth
from werkzeug.security import generate_password_hash, check_password_hash

# This time, we store hashed passwords. 
# Meaning that they can't be compromised if our code leaks.
USERS = {
    "alice": "scrypt:32768:8:1$HtB7iBw3ZGspBOHX$36879fd912db96322af2c33c3eb3fd0142ca2ec51988f332cc477b89c012449e5562487b410264b02d495d785780d0099e9130b42c4f61d7bc9166b93f7d1626",
    "bob": "scrypt:32768:8:1$j2rFpuXSr2D5t4Ij$9436a942b25f93054d3c96e54dcdd342c97f86515e09aafecd564984c22beb991029b70a89164215590ec131be4e13a12e043f4572ceb06576f86f925b1b9d65"
}

def auth_func(username, password):
    """ Authenticate the user using hashed passwords. """
    if username in USERS and check_password_hash(USERS[username], password):
        return True
    return False

app = Dash(__name__)
auth = BasicAuth(
    app, 
    auth_func=auth_func, # We now use auth_func in place of the user list.
    secret_key="something_like_nUGz8DZvb..."
)

app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated.")
])

if __name__ == "__main__":
    app.run(debug=True)
</code></pre>



<p>The result is visually the same for the user, but now passwords are not stored in clear. It also means that the original passwords are not shared with the developers having access to the Dash app code.</p>



<p>Much better! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h3 id='use-a-database'  id="boomdevs_5" class="wp-block-heading" >Use a database</h3>



<p>We can also use the authentication function to request an external database. The good thing with this solution is that the list of users can be dynamically managed.</p>



<p>Let&#8217;s create a simple script to initialize a <code>sqlite</code> database:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># init_db.py
import sqlite3
from werkzeug.security import generate_password_hash

DB_PATH = "users.db"

def init_db():
    """Create a tiny users table and seed two demo users if empty."""
    with sqlite3.connect(DB_PATH) as con:
        cur = con.cursor()
        cur.execute("""
            DROP TABLE IF EXISTS users;
            CREATE TABLE users (
                username TEXT PRIMARY KEY,
                password_hash TEXT NOT NULL,
                language TEXT NOT NULL
            )
        """)

        # Insert values
        cur.executemany(
            "INSERT INTO users(username, password_hash, language) VALUES (?, ?, ?)",
            [
                ("alice", generate_password_hash("secret123"), "en"),
                ("bob",   generate_password_hash("pa$$w0rd"), "fr"),
            ],
        )

if __name__ == "__main__":
    init_db()
    print("Database initialized")
</code></pre>



<p>Note that we directly save the hashed password in the <code>users</code> table.</p>



<p>Then, we simply query this database in our <code>auth_func</code> function to get the hashed password of a user, then compare it:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html
from dash_auth import BasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3

DB_PATH = "users.db"

def get_hash(username):
    """Fetch the stored hash for a username, or None."""
    with sqlite3.connect(DB_PATH) as con:
        cur = con.cursor()
        cur.execute("SELECT password_hash FROM users WHERE username = ?", (username,))
        row = cur.fetchone()
        return row[0] if row else None

def auth_func(username, password):
    """ Authenticate the user using hashed passwords. """
    hash_from_db = get_hash(username)

    if hash_from_db and check_password_hash(hash_from_db, password):
        return True
    return False

app = Dash(__name__)
auth = BasicAuth(
    app, 
    auth_func=auth_func,
    secret_key="something_like_nUGz8DZvb..."
)

app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />"),
])

if __name__ == "__main__":
    app.run(debug=True)
</code></pre>



<p>That&#8217;s it!</p>



<p>Now you can modify the list of users, modify passwords, etc. in the database without having to reload the application. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>It doesn’t need to be a <code>sqlite</code> database. You can also make HTTP requests to a database provider, or query any other type of database (Postgresql, MongoDB, &#8230;).</p>



<p class="callout">Keep in mind that the <code>auth_func</code> function is executed for every callback and every dash request. If the check takes too much time, think about caching solutions like <code>memoize</code>.</p>



<h2 id='why-you-can-t-logout'  id="boomdevs_6" class="wp-block-heading" >Why you can’t logout</h2>



<p><code>dash-auth</code> uses <strong>HTTP Basic Authentication</strong>, which is built into your browser.</p>



<ul class="wp-block-list">
<li>Your credentials (username + password) are cached.</li>



<li>The browser automatically re-sends them with every request.</li>



<li>There’s no “logout” button because the browser doesn’t expose a way to clear those credentials.</li>
</ul>



<p>The only way to log out? Close the browser tab or window.</p>



<h2 id='conclusion'  id="boomdevs_7" class="wp-block-heading" >Conclusion</h2>



<p>This tutorial helped you to secure a Dash app using the <code>dash_auth</code> package. </p>



<p><code>dash-auth</code> has its drawbacks, but it’s a fairly easy solution to add an authentication layer to a dashboard or a data app. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> However, I would not recommend using it for production apps that have very sensitive data. </p>



<p>If you need proper login/logout behavior (e.g., session expiration, multiple users switching), you’ll need a more advanced system such as:</p>



<ul class="wp-block-list">
<li><code>flask_login</code></li>



<li>OAuth (Google, GitHub…)</li>



<li>Enterprise identity providers (Okta, Auth0, Azure AD, …)</li>



<li><a href="https://plotly.com/dash/authentication/">Dash-Enterprise</a> (handles LDAP, SAML, OIDC)</li>
</ul>



<p>In the next tutorial, we will see <a href="https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/">how to use <code>dash-auth</code> for multi-pages Dash apps.</a></p>



<p>I hope to see you there!</p>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">How to secure a Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to use allow_duplicate (good and bad practices)</title>
		<link>https://dash-resources.com/how-to-use-allow_duplicate-good-and-bad-practices/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Wed, 16 Apr 2025 16:26:07 +0000</pubDate>
				<category><![CDATA[Intermediate level]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=852</guid>

					<description><![CDATA[<p>A few weeks ago, I was inspired to write an article about the allow_duplicate options and the possible drawbacks of using it. In this new [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-use-allow_duplicate-good-and-bad-practices/">How to use allow_duplicate (good and bad practices)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>A few weeks ago, I was inspired to write <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">an article</a> about the <code>allow_duplicate</code> options and the possible drawbacks of using it.</p>



<p>In this new article, I want to soften my original take and explain when to use <code>allow_duplicate=True</code> and when not to use it with clear examples. Of course, everything you&#8217;ll read here is based on my personal experience and might not cover all use cases! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#first-what-is-allow-duplicate=true">First, what is allow_duplicate=True?</a>
      </li>
      <li>
        <a href="#pros-and-cons-of-allow-duplicate=true">Pros and cons of allow_duplicate=True</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#harder-to-track-logic-maintainability-concerns">Harder to track logic &amp; Maintainability Concerns</a>
          </li>
          <li class="last">
            <a href="#potential-race-conditions">Potential race conditions</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#good-use-example-1-separate-concern">Good use example 1: separate concern</a>
      </li>
      <li>
        <a href="#good-use-example-2-partial-update">Good use example 2: partial update</a>
      </li>
      <li>
        <a href="#bad-use-example-1-harder-to-track">Bad use example 1: harder to track</a>
      </li>
      <li>
        <a href="#bad-example-2-race-condition">Bad example 2: race condition</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let&#8217;s dive in!</p>



<h2 id='first-what-is-allow-duplicate=true'  id="boomdevs_1" class="wp-block-heading" >First, what is <code>allow_duplicate=True</code>?</h2>



<p>In Dash, <strong>each Output</strong> is typically tied to exactly <strong>one</strong> callback. Attempting to attach an additional callback to the same Output will normally raise an error:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="407" src="https://dash-resources.com/wp-content/uploads/2025/04/image-1024x407.png" alt="" class="wp-image-853" srcset="https://dash-resources.com/wp-content/uploads/2025/04/image-1024x407.png 1024w, https://dash-resources.com/wp-content/uploads/2025/04/image-300x119.png 300w, https://dash-resources.com/wp-content/uploads/2025/04/image-768x305.png 768w, https://dash-resources.com/wp-content/uploads/2025/04/image.png 1242w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Illustration: every Dash developer has experienced this problem of “duplicate callback output” at least once.</figcaption></figure>



<p>However, since version 2.9 Dash provides an <strong>advanced</strong> setting—<code>allow_duplicate=True</code>—that lets you break that convention. This flag, placed in the <code>Output</code> declaration, tells Dash that you <em>intend</em> to create another callback targeting the <em>same</em> Output that was already used in a different callback.</p>



<p>Here is an example:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># First callback
@app.callback(
    Output('output', 'children', allow_duplicate=True),
    Input('input-1', 'value'),
    prevent_initial_call=True,
)
def update_output_1(value):
    # ... some processing 
    return value

# Second callback (duplicate output)
@app.callback(
    Output('output', 'children'),
    Input('input-2', 'value'),
    prevent_initial_call=True,
)
def update_output_2(value):
    # ... some other processing
    return value
</code></pre>



<p>Here, both callbacks target the same <code>Output</code> component property. Because the first callback has <code>allow_duplicate=True</code>, the second is allowed to share that same Output. We could just as easily add a third one, or even more.</p>



<p class="callout">Note that the <code>allow_duplicate=True</code> is required only for the first callback. But if you change the order callbacks are written in the code, you’ll need to update it. Setting it everywhere makes it less error-prone.</p>



<p>Using this option will also requires setting <code>prevent_initial_call=True</code> to ensures the callbacks don’t get triggered simultaneously (which is what we want to avoid, as you&#8217;ll read later).</p>



<h2 id='pros-and-cons-of-allow-duplicate=true'  id="boomdevs_2" class="wp-block-heading" >Pros and cons of <code>allow_duplicate=True</code></h2>



<p>Don’t get me wrong, <strong><code>allow_duplicate=True</code> is useful.</strong></p>



<p>Sometimes we have two distinct user interactions that need to update the same component. As the number of inputs grows, we quickly end up modifying a complex login into a &#8220;mega-callback&#8221;. It therefore become a nightmare to maintain and little changes can break many behaviors.</p>



<p>Instead, <code>allow_duplicate=True</code> enables splitting the responsibilities into smaller, clearer, more isolated callbacks. </p>



<p>But this goes against the first principle: having only one output per callback, which is generally a good design principle. Not following it has potential drawbacks and trade-offs to consider <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>



<h3 id='harder-to-track-logic-maintainability-concerns'  id="boomdevs_3" class="wp-block-heading" >Harder to track logic &amp; <strong>Maintainability Concerns</strong></h3>



<p>Normally in Dash, each property is updated by exactly one callback, so you can always look at that callback to know “what logic is behind this value.” Breaking that rule can lead to confusion: “Wait, which callback is controlling the output now? Or is it both?”</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1920" height="1116" src="https://dash-resources.com/wp-content/uploads/2025/04/image-1.png" alt="" class="wp-image-854" style="width:627px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/04/image-1.png 1920w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-300x174.png 300w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-1024x595.png 1024w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-768x446.png 768w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-1536x893.png 1536w" sizes="auto, (max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption">Illustration: a small callback mess.</figcaption></figure>
</div>


<p>Multiple callbacks returning to the same output can significantly complicate debugging and future maintenance. Other developers (or a future you) might have difficulty quickly discerning how that one visible component is being updated in different contexts.</p>



<h3 id='potential-race-conditions'  id="boomdevs_4" class="wp-block-heading" ><strong>Potential race conditions</strong></h3>



<p>If you have multiple callbacks writing to the same component property at roughly the same time, you may get <a href="https://en.wikipedia.org/wiki/Race_condition">race conditions</a>—where whichever finishes last overwrites the other. This can cause flickering, inconsistent states, or unexpected behavior if not carefully managed.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1440" height="654" src="https://dash-resources.com/wp-content/uploads/2025/04/image-2.png" alt="" class="wp-image-855" style="width:653px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/04/image-2.png 1440w, https://dash-resources.com/wp-content/uploads/2025/04/image-2-300x136.png 300w, https://dash-resources.com/wp-content/uploads/2025/04/image-2-1024x465.png 1024w, https://dash-resources.com/wp-content/uploads/2025/04/image-2-768x349.png 768w" sizes="auto, (max-width: 1440px) 100vw, 1440px" /><figcaption class="wp-element-caption">Illustration: the &#8220;race condition&#8221; means the result will depend on which callback finishes first, which can lead to unexepcted results (non-deterministic).</figcaption></figure>
</div>


<p>This is something that I saw happening and I wrote an article about it in detail here: <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">The dark side of allow_duplicates &amp; making calbacks sequential</a>.</p>



<h2 id='good-use-example-1-separate-concern'  id="boomdevs_5" class="wp-block-heading" >Good use example 1: separate concern</h2>



<p>Let’s take an example of fully separated logic.</p>



<p>The following is a true example of a login/register process that I experienced in my app:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("session_id", "data", allow_duplicate=True),
    [
        Input("login_button", "n_clicks"),
        State("login_email", "value"),
        State("login_password", "value"),
    ],
    prevent_initial_call=True,
)
def user_identification(button_n_clicks, username, password):
    """This callback handles the login of a user given a username and password """
    # ... handle login
    session_id = uuid.uuid4()
    return session_id

@callback(
    Output("session_id", "data", allow_duplicate=True),
    [
        Input("register_button", "n_clicks"),
        State("register_email", "value"),
        State("register_password", "value"),
        State("register_lastname", "value"),
        State("register_firstname", "value"),
        State("register_company", "value"),
        State("register_tel", "value"),
    ],
    prevent_initial_call=True,
)
def user_registration(button_n_clicks, email, password, lastname, firstname, company, telephone):
    """This callback handles the registration of a new user"""
    # ... handle registration
    session_id = uuid.uuid4()
    return session_id
</code></pre>



<p>Both callbacks handle multiple inputs (<em>email, password, firstname, lastname</em>, etc.) and they return a <code>session_id</code>, that is stored in a session <code>dcc.Store</code>.</p>



<p>This <code>session_id</code> will then trigger another set of callbacks that are related to the loading of an user: displaying its name, loading its preferences, etc.</p>



<p>What’s interesting about this example is that it actually benefited from using <code>allow_duplicate</code>. The two callbacks are different and process a different set of inputs, thus, it makes sense to have two separate callbacks.</p>



<p>This is what it looked like if merged into one large callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("session_id", "data"),
    [
        Input("login_button", "n_clicks"),
        Input("register_button", "n_clicks"),
    ],
    [
        State("login_email", "value"),
        State("login_password", "value"),
        State("register_email", "value"),
        State("register_password", "value"),
        State("register_lastname", "value"),
        State("register_firstname", "value"),
        State("register_company", "value"),
        State("register_tel", "value"),
    ],
    prevent_initial_call=True,
)
def user_identification_or_registration(login_n_clicks, register_n_clicks, username, password1, email, password2, lastname, firstname, company, telephone):
    """This callback handle the login of a user given a username and password """
    if ctx.triggered_id == "login_button":
        # ... handle login
        session_id = uuid.uuid4()
        return session_id
    elif ctx.triggered_id == "register_button":
        # ... handle registration
        session_id = uuid.uuid4()
        return session_id
</code></pre>



<p>But large callbacks also have their own drawbacks: harder to maintain, multiple inputs to handle, etc… Plus, we would be sending unnecessary information with every callback trigger—making it less efficient</p>



<p>In this case, <code>allow_duplicate</code> proves really helpful in reducing the complexity and making a more readable and maintainable code.</p>



<p class="callout">Prior the introduction of <code>allow_duplicate</code>, I actually handled this with two <a href="http://dcc.Store">dcc.Store</a> : <code>session_id_login</code> and <code>session_id_register</code>, which were both triggering a callback updating a single output: <code>session_id</code>. They were used as temporary storage —but that was less efficient and required one round trip to the server as well as one unecessary callback execution.</p>



<h2 id='good-use-example-2-partial-update'  id="boomdevs_6" class="wp-block-heading" >Good use example 2: partial update</h2>



<p>The previous example illustrated how <code>allow_duplicate</code> can help separate callbacks when logic is different. It’s more about callback designing, but there are solutions to do it without <code>allow_duplicate</code>.</p>



<p>The following example <strong>could not be possible</strong> without the help of <code>allow_duplicate</code> .</p>



<p>Imagine you have a figure. And you want to update only the title of a figure without redrawing the whole figure. The code would look like:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output('graph', 'figure', allow_duplicate=True),
    Input('dropdown', 'value'),
    prevent_initial_call=True,
)
def update_graph(value):
    # Download very large data from a server
    df = pd.read_csv('https://.../data.csv')
    fig = px.line(df, x='date', y='value', color='category')
    return fig

@callback(
    Output('graph', 'figure'),
    Input('title_input', 'value'),
    prevent_initial_call=True,
)
def update_title(title):
    # Just modify the title with Patch()
    fig = Patch()
    fig['layout']['title'] = title
    return fig

</code></pre>



<p>In <code>update_graph</code>, we load a large dataset and return a figure. In the other callback <code>update_title</code>, we only update the title of the figure and return it, using <code>Patch()</code>.</p>



<p>What’s wonderful here is that the use of <code>allow_duplicate</code> and <code>Patch()</code> conjointly helped updating the figure without the need to reload the whole data and create the entire figure again.</p>



<p>It’s a more efficient solution, that would translate in a more responsive application and a better user experience.</p>



<p class="callout">Lean more about partial updates here: <a href="https://dash.plotly.com/partial-properties">https://dash.plotly.com/partial-properties</a></p>



<p>In this case, <code>allow_duplicate</code> enabled something that wasn’t possible before.</p>



<h2 id='bad-use-example-1-harder-to-track'  id="boomdevs_7" class="wp-block-heading" >Bad use example 1: harder to track</h2>



<p>The following is a fictitious bad example, and/or bad callback design, where two callbacks update the same container:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("results_container", "children", allow_duplicate=True),
    Input("search_button", "n_clicks"),
    prevent_initial_call=True,
)
def search_results(n_clicks):
    """This callback searches for results when the search button is clicked"""
    if n_clicks is None:
        raise PreventUpdate
    
    # some processing
    
    return [
        html.Div("Search results from button click"),
        html.Div([html.Span(f"Result {i}") for i in range(5)])
    ]

@callback(
    Output("results_container", "children", allow_duplicate=True),
    Input("filter_dropdown", "value"),
    prevent_initial_call=True,
)
def filter_results(filter_value):
    """This callback filters results when dropdown value changes"""
    if filter_value is None:
        raise PreventUpdate
    
    # some processing
    
    return [
        html.Div(f"Filtered results for: {filter_value}"),
        html.Div([html.Span(f"Filtered item {i}") for i in range(3)])
    ]
</code></pre>



<p>Both <code>search_results</code> and <code>filter_results</code> can be triggered . If both are triggered at the same time, we will have either one result or the other (the last callback being executed), which is misleading.</p>



<p>The issue here is that we’re using one single container, <code>result_container</code>, to display two different solution. And <code>allow_duplicate=True</code> made it possible.</p>



<p class="callout">To be clear, the above code will work. It’s just that in my opinion, this is not a good use of <code>allow_duplicates</code> for the sake of maintainability and callback logic.</p>



<p>Instead, it would have been better to just have two separate containers (e.g. a new <code>search_result_container</code> and <code>filter_result_container</code>) or to merge the logic into one callback. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='bad-example-2-race-condition'  id="boomdevs_8" class="wp-block-heading" >Bad example 2: race condition</h2>



<p>This example will illustrate the <a href="https://en.wikipedia.org/wiki/Race_condition">race condition</a> problem. </p>



<p>Imagine you need to store the number of clicks on two buttons in a list. The first button increments the first count, and the second button updates the second count:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Our dcc store is like this:
results_store = [0, 0]

@callback(
    Output("results_store", "data", allow_duplicate=True),
    Input("button1", "n_clicks"),
    State("results_store", "data"),
    prevent_initial_call=True,
)
def update_store_from_button1(n_clicks, current_data):
    """ Store the number of clicks on button1"""
    # Sleep randomly to simulate network latency
    time.sleep(random.uniform(0.5, 2))

		# Update button 1 count
    current_data[0] = n_clicks
    return current_data

@callback(
    Output("results_store", "data", allow_duplicate=True),
    Input("button2", "n_clicks"),
    State("results_store", "data"),
    prevent_initial_call=True,
)
def update_store_from_button2(n_clicks, current_data):
    """ Store the number of clicks on button2"""
    # Sleep randomly to simulate network latency
    time.sleep(random.uniform(0.5, 2))
    
    # Update button 2 count
    current_data[1] = n_clicks
    return current_data
</code></pre>



<p>Each callback is triggered by its button, uses the state of <code>current_data</code> and returns the updated value.</p>



<p>Let’s give an example:</p>



<ul class="wp-block-list">
<li>click on button1 → … add one to the number of clicks (current data state is <code>[0, 0]</code>) … → the callback returns <code>[1, 0]</code></li>



<li>then click on button2 → … add one to the number of clicks (current data state is <code>[1, 0]</code>) … → the callback returns <code>[1, 1]</code></li>
</ul>



<p>This is all good because we click and wait for the callback to be fully executed. Now, what if we click to both buttons at the same time ?</p>



<ul class="wp-block-list">
<li>click on button1 → … add one to the number of clicks (current data state is <code>[1, 1]</code>) … → the callback returns <code>[2, 1]</code></li>



<li>click on button2 → … add one to the number of clicks (current data state is <code>[1, 1]</code>) … → the callback returns <code>[1, 2]</code></li>
</ul>



<p>Depending on which callback finishes first, we’ll get different results: [2, 1] or [1, 2].</p>



<p>You can try a live demo below (or click <a href="https://scripts.dash-resources.com/sequential/app_btns.py/">here</a>):</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/sequential/app_btns.py/" width="100%" height="400" frameBorder="0"></iframe>



<p class="callout">In this example, I chose to simulate network latency with <code>time.sleep()</code> so that the problem is emphasized. When testing locally, you might not notice the problem—but it can become obvious once deployed to production.</p>



<p>In this case, a better solution would have been to</p>



<ul class="wp-block-list">
<li>use <code>Patch()</code> to be sure we do not rely on states</li>



<li>use different <code>dcc.Store</code> to store the two information.</li>
</ul>



<p>I hope this example helped to understand race conditions. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Again, <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">this other article</a> also illustrates this problem.</p>



<h2 id='conclusion'  id="boomdevs_9" class="wp-block-heading" >Conclusion</h2>



<p>I hope this article helped you to understand what are the good and bad practices with this powerful option, <code>allow_duplicate</code>.</p>



<p>If you’re unsure when to use it, here are a few rules I’ve come up with:</p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-ad2f72ca wp-block-group-is-layout-flex">
<ul class="wp-block-list">
<li>If you have two separate callback logic that depend on which inputs are triggering the callback (i.e. with <code>ctx.triggered</code>), and have a different set of inputs, then creating two callbacks with <code>allow_duplicate</code> may be a good solution <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>If the callbacks (or more) are never supposed to be triggered at the same time, you are also safe to use <code>allow_duplicate</code> but consider using different outputs if possible <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>If you need to update partially a component (e.g. a figure) using Patch, then <code>allow_duplicate</code> is your only option. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>
</ul>
</div>



<p>I you have questions or want to join the discussion, I created at topic here: <a href="https://community.plotly.com/t/the-dark-side-of-allow-duplicates-making-callbacks-sequential/91459">plotly forum</a>.</p>



<p>— Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-use-allow_duplicate-good-and-bad-practices/">How to use allow_duplicate (good and bad practices)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>The dark side of allow_duplicate &#038; making callbacks sequential</title>
		<link>https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Tue, 25 Mar 2025 16:29:47 +0000</pubDate>
				<category><![CDATA[Intermediate level]]></category>
		<category><![CDATA[callbacks]]></category>
		<category><![CDATA[storage]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=828</guid>

					<description><![CDATA[<p>I was inspired by this question on the Plotly community forum to talk about sequential callbacks and, more broadly, callback design with or without allow_duplicate. [...]</p>
<p>L’article <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">The dark side of allow_duplicate &amp; making callbacks sequential</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>I was inspired by <a href="https://community.plotly.com/t/execution-of-callbacks-writting-to-a-single-dcc-store/91448/3">this question</a> on the Plotly community forum to talk about sequential callbacks and, more broadly, callback design with or without <code>allow_duplicate</code>.</p>



<p>Ensuring that some information is stored without desynchronization issues with callbacks is also a problem that I encountered a few times when developing Dash applications. As a result, I want to discuss here a few solutions and when to approach them.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#the-problem">The problem</a>
      </li>
      <li>
        <a href="#solution-1-chaining-callbacks">Solution 1: chaining callbacks</a>
      </li>
      <li>
        <a href="#solution-2-rewriting-callbacks">Solution 2: rewriting callbacks</a>
      </li>
      <li>
        <a href="#solution-3-using-intermediary-stores">Solution 3: using intermediary stores</a>
      </li>
      <li>
        <a href="#solution-4-using-partial-update">Solution 4: using Partial Update</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let&#8217;s dive in <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='the-problem'  id="boomdevs_1" class="wp-block-heading" >The problem</h2>



<p>Imagine you have a <code>dcc.Store("user_data")</code> that stores something like <code>{"firstname": "John", "lastname": "Doe", "age": 50, ...}</code>. This Store is set via multiple inputs: first_name, last_name, age, …It is a large dictionary and you might have multiple callbacks updating it.</p>



<p>To update this <code>user_data</code> from many inputs, you can set <code>allow_duplicate=True</code>. That way, you are not restricted to only one callback for this output. You just need to get the full <code>user_data</code> back each time (as a State), modify it, and return it.</p>



<p>The code would be something like:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("first_name", "value"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_first_name(first_name, user_data):

    # Just update user_data with the first name
    if first_name: 
        user_data["first_name"] = first_name
        return user_data

    raise PreventUpdate

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("last_name", "value"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_last_name(last_name, user_data):

    # Just update user_data with the last name
    if last_name: 
        user_data["last_name"] = last_name
        return user_data

    raise PreventUpdate

# ... other callbacks</code></pre>



<p>For the moment, everything is fine. But a problem starts appearing if you trigger all inputs at the same time, e.g. to set initial values:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># A callback to simulate the desynchro problem.
# As it triggers the first name and last name input as the same time
@app.callback(
    Output("first_name", "value"),
    Output("last_name", "value"),
    Input("load_data", "n_clicks"),
)
def load_data(n_clicks):
    if n_clicks:
        return "Mickael", "Jackson"
    raise PreventUpdate</code></pre>



<p>A de-synchronization problem appears, between the inputs and the result in the <code>user_dict</code> memory store. The inputs have the updated values, not the stored memory <code>user_dict</code>.</p>



<p>You can try it below (or <a href="https://scripts.dash-resources.com/sequential/app.py/">click here</a>):  </p>



<ul class="wp-block-list">
<li>if you enter &#8220;john&#8221; and &#8220;doe&#8221;, then everything gets updated correctly.</li>



<li>then click on &#8220;load&#8221; data. the <code>user_data</code> should contains firstname = &#8220;mickael&#8221; and lastname = &#8220;jackson&#8221;, but instead some discrepency appears.</li>
</ul>



<iframe loading="lazy" src="https://scripts.dash-resources.com/sequential/app.py/" width="100%" height="600" frameBorder="0"></iframe>



<p><strong>Why this problem appears.</strong></p>



<p>Both callbacks are run at the same time. If <code>update_first_name</code> is triggered at the same time <code>update_last_name</code> is triggered, the state value of <code>user_data</code> do not contains the updated first name. It also works the way around, which explains that sometimes we don&#8217;t have the first name, and sometimes the last name. it depends which callback is fired first.</p>



<p>And having <code>Input("user_data", "value")</code> instead of <code>State("user_data", "value")</code> might be possible in some cases, but you would end up with some weird behavior due to the circular pattern. If you have 5 or 10 callbacks following the same scheme, your app will basically blow up because of all the callbacks triggered in all directions. </p>



<p>Here is a video example with 4 inputs:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1964 / 1266;" width="1964" controls src="https://dash-resources.com/wp-content/uploads/2025/03/dash-too-many-circular-callbacks.mp4"></video><figcaption class="wp-element-caption">Illustration: 4 inputs with circular callbacks. Each input modification generates a lot of callback requests.</figcaption></figure>



<p><strong>Notice how many callbacks </strong>are triggered with a single change in one input (1 HTTP request = 1 callback)! Because of the circular scheme, callbacks get triggered unecessarily.</p>



<p>The solution can be to ensure that callbacks are executed sequentially: first <code>update_first_name</code>, and then <code>update_last_name</code>, with the updated value of <code>user_data</code>. That&#8217;s actually what it &#8220;seems to be&#8221;, but the real solution is to think about the problem differently.</p>



<p>But let&#8217;s see why.</p>



<h2 id='solution-1-chaining-callbacks'  id="boomdevs_2" class="wp-block-heading" >Solution 1: chaining callbacks</h2>



<p>How do we make callbacks sequential?</p>



<p>Chaining is the simplest form of sequential callbacks. In that case, you just need to connect one of the outputs of the first callback as an input for the second callback (A -&gt; o -&gt; B -&gt; o).</p>



<p>But as I said previously, we cannot set <code>user_data</code> as an input for the second callback (because of circular dependencies).</p>



<p>Instead, we can use an intermediary <code>dcc.Store</code>, that will just be used as a trigger. I think a timestamp is a good fit for this purpose:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("user_data", "data", allow_duplicate=True),
    Output("intermediary_timestamp", "data"),
    Input("first_name", "value"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_first_name(first_name, user_data):

    # Just update user_data with the first name
    if first_name: 
        user_data["first_name"] = first_name
        return user_data, time.time()

    raise PreventUpdate

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("last_name", "value"),
    Input("intermediary_timestamp", "data"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_last_name(last_name, timestamp, user_data):

    # Just update user_data with the last name
    if last_name: 
        user_data["last_name"] = last_name
        return user_data

    raise PreventUpdate</code></pre>



<p>In this example, the <code>update_first_name</code> callback will return the <code>user_data</code> and a timestamp. The dash-renderer will know that it has to wait before triggering <code>update_last_name</code> because <code>intermediary_timestamp</code> must be ready before executing it.</p>



<p class="callout"><strong>Pro-tip:</strong> we could have used the property <code>modified_timestamp</code> of our <code>user_data</code> dcc.Store component as an input. i.e., replacing <code>Input("intermediary_timestamp", "data")</code> by <code>Input("user_data", "modified_timestamp")</code>. That works too and we don&#8217;t even need the intermediary dcc.Store <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='solution-2-rewriting-callbacks'  id="boomdevs_3" class="wp-block-heading" >Solution 2: rewriting callbacks</h2>



<p>But wait wait wait… Should we really need to chain callbacks?<br>Often the best option is to handle the processing of the two callbacks inside the same callback.</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("user_data", "data"),
    Input("first_name", "value"),
    Input("last_name", "value"),
    # .. other possible inputs
    State("user_data", "data"),
)
def update_user_data(first_name, user_data):

    # Just update user_data with the first name
    if first_name: 
        user_data["first_name"] = first_name
    if last_name:
        user_data["last_name"] = last_name
    # .. other possible values

    return user_data</code></pre>



<p>And that solves the synchronization problem.</p>



<p>However, it can happen that you don&#8217;t want to get the value from the input if it wasn&#8217;t really triggered.</p>



<p>Hopefully, there is a way to filter which input was really triggered, using <code>ctx.triggered_prop_ids</code> (<a href="https://dash.plotly.com/advanced-callbacks#determining-which-input-has-fired-with-dash.callback_context">documentation link</a>). Let&#8217;s see an example:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def slow_processing(user_data):
    # Simulate some long processing, e.g. requesting a database
    time.sleep(4)
    return "Some information"


@callback(
    Output("user_data", "data"),
    Input("first_name", "value"),
    Input("last_name", "value"),
    Input("info_button", "n_clicks"),
    # .. other possible inputs
    State("user_data", "data"),
)
def update_user_data(first_name, last_name, n_clicks, user_data):

    # Identify which inputs were triggered
    first_name_triggered = "first_name" in ctx.triggered_prop_ids.values()
    last_name_triggered = "last_name" in ctx.triggered_prop_ids.values()
    button_triggered = "info_button" in ctx.triggered_prop_ids.values()

    # Update user_data accordingly
    if first_name_triggered and first_name: 
        user_data["first_name"] = first_name
    if last_name_triggered and last_name:
        user_data["last_name"] = last_name

    if button_triggered and n_clicks:
        # An information that takes time to retrieve
        # You want to compute it only if button was *really* triggered
        info = slow_processing(user_data)  
        user_data["info"] = info

    # .. other possible inputs being hanled

    return user_data</code></pre>



<p>The good thing is that this solution is scalable: we can continue adding inputs and keys to our <code>user_data</code> easily. And we get rid of <code>allow_duplicate=True</code> which is useful but also leads to &#8220;callback bad design&#8221;. I&#8217;ll come back to this later.</p>



<h2 id='solution-3-using-intermediary-stores'  id="boomdevs_4" class="wp-block-heading" >Solution 3: using intermediary stores</h2>



<p>Part of the problem that we have here is the use of one memory store for multiple information. </p>



<p>So instead of trying to store everything inside the same callback, we could actually split the <code>user_data</code> dict into as many stores as required: <code>user_first_name</code>, <code>user_last_name</code>, <code>user_age</code>, <code>user_info</code>, etc.</p>



<p>Then, we would have to merge all this information into one callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">
app.layout = [
    # ...
    dcc.Store(id="user_first_name"),
    dcc.Store(id="user_last_name"),
    dcc.Store(id="user_age"),
    dcc.Store(id="user_info"),
    # ... other values
]

@callback(
    Output("user_first_name", "data"),
    Input("first_name", "value")
)
def update_first_name(first_name):
    if first_name: 
        return first_name
    raise PreventUpdate

@callback(
    Output("user_last_name", "data"),
    Input("last_name", "value")
)
def update_last_name(last_name):
    if last_name: 
        return last_name
    raise PreventUpdate

@callback(
    Output("user_age", "data"),
    Input("age", "value")
)
def update_age(age):
    if age: 
        return age
    raise PreventUpdate

@callback(
    Output("user_info", "data"),
    Input("info_button", "n_clicks"),
    State("user_data", "data")
)
def update_info(n_clicks, user_data):
    if n_clicks:
        info = slow_processing(user_data) 
        return info
    raise PreventUpdate

# ... other callbacks 

# then we update the user_data:
@callback(
    Output("user_data", "data"),
    Input("user_first_name", "data"),
    Input("user_last_name", "data"),
    Input("user_age", "data"),
    Input("user_info", "data"),
    prevent_initial_call=True,
)
def update_user_data(first_name, last_name, age, info):
    return {
        "first_name": first_name,
        "last_name": last_name,
        "age": age,
        "info": info,
        # ... other properties and values?
    }</code></pre>



<p>If all callbacks are triggered at the same time, <code>update_user_data</code> will be the last one triggered, and all input values will be filled before it can run. That&#8217;s a good way to make callbacks <em>sequential</em>.</p>



<p>The <strong>good</strong> thing about this solution is that we can get rid of <code>allow_duplicate=True</code> too. The <strong>bad</strong> thing is that it is poorly scalable: we would need to add as many stores and callbacks as we have keys to update in <code>user_data</code>.</p>



<p>As you can see, the initial problem was made possible because of <code>allow_duplicate=True</code>. If it wasn&#8217;t an option, we might have used this solution in the first place. And even if it&#8217;s a verbose solution, it&#8217;s a good simple solution. <strong><code>allow_duplicate=True</code> is a fortunate option to use in some cases, but it often leads to bad callback design.</strong></p>



<p class="callout"><strong>Pro-tip:</strong> 99% of the time you don&#8217;t need <code>allow_duplicate=True</code>. Try to avoid it as much as possible. It can creates callback mess.</p>



<h2 id='solution-4-using-partial-update'  id="boomdevs_5" class="wp-block-heading" >Solution 4: using Partial Update</h2>



<p>The above solutions will rely on the fact that we get <code>user_data</code> as a <code>State</code>.<br>But what if <code>user_data</code> is very large? Dash callbacks are HTTP requests, so a large input or state would result in a large HTTP request. Depending on the user&#8217;s bandwidth, it can take time.</p>



<p>Instead, we could use <code>Patch()</code> (<a href="https://dash.plotly.com/partial-properties">documentation link</a>) to only update one key at a time, not the whole <code>user_data</code> dictionary.</p>



<p>Let&#8217;s see the code:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Patch

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("first_name", "value"),
    prevent_initial_call=True,
)
def update_first_name(first_name):

    # Just update user_data with the first name
    if first_name: 
        user_data = Patch()
        user_data["first_name"] = first_name
        return user_data

    raise PreventUpdate

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("last_name", "value"),
    prevent_initial_call=True,
)
def update_last_name(last_name):

    # Just update user_data with the last name
    if last_name: 
        user_data = Patch()
        user_data["last_name"] = last_name
        return user_data

    raise PreventUpdate</code></pre>



<p>And that&#8217;s it. :-). <strong>So, how does it works ?</strong> </p>



<ul class="wp-block-list">
<li>In the other solutions, Dash will send the whole <code>user_data</code> to be modified in a callback, gets the new full value, and updates in its storage.  </li>



<li>With Patch(), Dash will just send the input and get just the piece of information that was modified. It then updates the full object in its storage with the new piece of information. </li>



<li>Unless two callbacks modifies the same piece of information (<em>come&#8217;on, you want problems</em>), this solution do not require sequential execution.</li>
</ul>



<p>It&#8217;s maybe the most powerful solution, especially if <code>user_data</code> is very large. It can work with solution n°1, n°2 and n°3. We still use <code>allow_duplicate</code>, but it is not a problem anymore.</p>



<h2 id='conclusion'  id="boomdevs_6" class="wp-block-heading" >Conclusion</h2>



<p>So what&#8217;s the best solution? As always, it depends on your use case.</p>



<p>But here key takeaways:</p>



<ul class="wp-block-list">
<li>If you can, try to avoid using <code>allow_duplicate=True</code> and have one callback update one output</li>



<li>If you can, try to merge multiple callbacks into one callback. It&#8217;s more efficient, and you will de-facto solve the synchronization problem</li>



<li>If you have large data, <code>Patch()</code> is probably the best solution.</li>
</ul>



<p>I hope this article helped you learn about different approaches and how things can get complex with Dash callbacks. Feel free to ask questions or join the discussion here. </p>



<p>Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">The dark side of allow_duplicate &amp; making callbacks sequential</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/03/dash-too-many-circular-callbacks.mp4" length="558049" type="video/mp4" />

			</item>
		<item>
		<title>Understanding dcc.Store in Dash Plotly</title>
		<link>https://dash-resources.com/understanding-dcc-store-in-dash-plotly/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Wed, 05 Mar 2025 16:59:05 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[storage]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=792</guid>

					<description><![CDATA[<p>Dash applications often require sharing data between callbacks without rerunning expensive computations or relying on an external database. This is where dcc.Store comes in to [...]</p>
<p>L’article <a href="https://dash-resources.com/understanding-dcc-store-in-dash-plotly/">Understanding dcc.Store in Dash Plotly</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Dash applications often require sharing data between callbacks without rerunning expensive computations or relying on an external database. This is where <code>dcc.Store</code> comes in to store your app data on the client side.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'><ul>
  <li class="first">
    <span></span>
    <ul class="menu_level_1">
      <li class="first">
        <a href="#what-is-dcc-store-and-why-do-we-need-it">What is dcc.Store, and why do we need it?</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#a-bit-of-history">A bit of history</a>
          </li>
          <li>
            <a href="#introduction-to-dcc-store">Introduction to dcc.Store</a>
          </li>
          <li class="last">
            <a href="#why-it-is-needed">Why it is needed</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#understanding-dcc-store-storage-types">Understanding dcc.Store Storage Types</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#how-much-can-you-store">How much can you store?</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#how-to-debug-dcc-store">How to debug dcc.Store</a>
      </li>
      <li class="last">
        <a href="#some-best-practices-for-dcc-store">Some best practices for dcc.Store</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#&#x27a1;-serialize-data-properly"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serialize data properly</a>
          </li>
          <li>
            <a href="#&#x27a1;-don-t-store-large-objects-or-dataframes"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Don’t store large objects or DataFrames</a>
          </li>
          <li>
            <a href="#&#x27a1;-use-the-right-storage-type"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Use the right storage type</a>
          </li>
          <li class="last">
            <a href="#&#x27a1;-trigger-callbacks-efficiently"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Trigger callbacks efficiently</a>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li class="last">
    <a href="#conclusion">Conclusion</a>
  </li>
</ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>In this tutorial, we’ll explore what <code>dcc.Store</code> is, how it works, and best practices for using it effectively in Dash applications.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="callout"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> <strong>Level up your Dash skills!</strong> <strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> I wrote a full course to help you <a href="https://dash-resources.com/dash-plotly-course/">learn how to think and design a Dash app with simple and advanced callbacks</a>. Real pro-tips, from a real human expert!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='what-is-dcc-store-and-why-do-we-need-it'  id="boomdevs_1" class="wp-block-heading" >What is <code>dcc.Store</code>, and why do we need it?</h2>



<h3 id='a-bit-of-history'  id="boomdevs_2" class="wp-block-heading" >A bit of history</h3>



<p>Initially, people used to store data for Dash apps in hidden HTML components. However, that wasn’t an ideal solution: there was no way to retain data after reloading the page or closing the tab, and embedding large amounts of data in HTML could bloat the app and cause serious slowdowns.</p>



<p>As a result, the Plotly team introduced <code>dcc.Store</code> in Dash 0.32, and it has become one of the most essential components.</p>



<h3 id='introduction-to-dcc-store'  id="boomdevs_3" class="wp-block-heading" >Introduction to dcc.Store</h3>



<p>The <code>dcc.Store</code> component serves multiple important functions: it enables data sharing between callbacks without repeatedly passing large objects, stores intermediate calculation results to prevent costly recomputations, and can maintain data persistence during page reloads or multi-page navigation—when configured with the appropriate storage type.</p>



<p>It has four key properties:</p>



<ul class="wp-block-list">
<li><strong><code>id</code></strong>: Like any other regular Dash component, this is used for callbacks.</li>



<li><strong><code>storage_type</code></strong>: Either <code>memory</code>, <code>session</code>, or <code>local</code> (more on this below).</li>



<li><strong><code>data</code></strong>: The initial value for the store when the app loads.</li>



<li><strong><code>modified_timestamp</code></strong>: The latest modification timestamp.</li>
</ul>



<p>You can create as many <code>dcc.Store</code> components as you need and include them in your app’s layout. Here’s an example:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, dcc, html, Input, Output, State

app = Dash(__name__)

app.layout = html.Div([
    html.H1("My app"),
    # ... other components

    # Memory stores examples
    dcc.Store("user_id_store", storage_type="memory"),
    dcc.Store("dashboard_option_store", storage_type="local", data=False),
])

# A callback example 
@app.callback(
    Output("user_id_store", "data"),
    Input("button_login", "n_clicks"),
)
def login_callback(n_clicks):
    if n_clicks:
        # ... get user_id from login form
        return user_id
        
# Another callback example with user_id as state
@app.callback(
    Output("dashboard_option_store", "data"),
    Input("dashboard_option_dropdown", "value"),
    State("user_id_store", "data"),
)
def dashboard_option_callback(dashboard_option, user_id):
    if user_id:
        # ... get dashboard option for this specific user_id
        return dashboard_option</code></pre>



<p>In this example, we set up a <code>user_id_store</code> component to keep the current user ID. That information can then be passed as state to many callbacks requiring knowledge of which user is performing a particular action, such as selecting a dashboard option.</p>



<h3 id='why-it-is-needed'  id="boomdevs_4" class="wp-block-heading" >Why it is needed</h3>



<p>Dash operates as a stateless framework, meaning each callback function operates independently—processing requests and discarding user-specific data afterward. This architecture makes global variables ineffective for data persistence across callback executions (you should never use global variables in Dash apps!).</p>



<p>The <code>dcc.Store</code> component provides client-side storage for JSON-serializable data directly in the user’s browser, making it easy to retain and share data across multiple interactions (i.e., callbacks). It is perfect for storing IDs, or small size data.  </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1718" height="882" src="https://dash-resources.com/wp-content/uploads/2025/03/image.png" alt="" class="wp-image-793" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image.png 1718w, https://dash-resources.com/wp-content/uploads/2025/03/image-300x154.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-1024x526.png 1024w, https://dash-resources.com/wp-content/uploads/2025/03/image-768x394.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-1536x789.png 1536w" sizes="auto, (max-width: 1718px) 100vw, 1718px" /><figcaption class="wp-element-caption">Illustration: A callback execution involving client-side stored data (with <code>dcc.Store</code>) and database data retrieval and processing.</figcaption></figure>



<p>However, if you want to maintain access to larger data between interactions (i.e. callbacks), you will need alternative storage methods like an external database (Redis, SQLite, PostgreSQL, MongoDB) because stored data on the clientside would require transmitting it over the network every time, which could be a huge bottleneck.</p>



<p class="callout"><strong>Learn</strong> best practices for callbacks here: <a href="https://dash-resources.com/dash-callbacks-best-practices-with-examples/">Dash callbacks best practices (with examples)</a> </p>



<h2 id='understanding-dcc-store-storage-types'  id="boomdevs_5" class="wp-block-heading" >Understanding <code>dcc.Store</code> Storage Types</h2>



<p>A key feature of <code>dcc.Store</code> is its flexibility in how data is stored. It offers three different storage types, each with unique characteristics:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Three different ways to initialize a store
dcc.Store(id='memory-store', storage_type='memory')  # Default
dcc.Store(id='session-store', storage_type='session')
dcc.Store(id='local-store', storage_type='local')</code></pre>



<p>Each storage type serves different purposes:</p>



<ul class="wp-block-list">
<li><strong>Memory (<code>storage_type='memory'</code>)</strong>: Data persists only for the current page session. If the user refreshes the page, the data is lost. This is the default and is best for temporary data that can be regenerated easily.</li>



<li><strong>Session (<code>storage_type='session'</code>)</strong>: Data persists across page refreshes within the same browser tab but is cleared when that tab (or the browser) is closed. This uses the browser’s <code>sessionStorage</code> API.</li>



<li><strong>Local (<code>storage_type='local'</code>)</strong>: Data persists indefinitely, even after the browser is closed and reopened. This uses the browser’s <code>localStorage</code> API and is great for user preferences or application settings.</li>
</ul>



<p>Your choice depends on your specific requirements. For instance, use <code>local</code> for user preferences that should persist across sessions, <code>session</code> for data that should survive a refresh but not a browser restart, and <code>memory</code> for transient data like intermediate computational results.</p>



<p class="callout"><strong>Pro-tip</strong>: It can be helpful to add a suffix like <code>-store</code> or <code>_memory</code> to all your <code>dcc.Store</code> IDs. This makes it easy to identify which inputs and outputs are stores in your callbacks.</p>



<h3 id='how-much-can-you-store'  id="boomdevs_6" class="wp-block-heading" >How much can you store?</h3>



<p>The amount of data that can be stored in <code>dcc.Store</code> depends on the selected <code>storage_type</code>:</p>



<ul class="wp-block-list">
<li><strong>Memory</strong>: Limited only by the browser’s available memory but cleared on page refresh.</li>



<li><strong>Session and Local</strong>: These rely on the browser’s <code>sessionStorage</code> and <code>localStorage</code> APIs, which typically allow up to <strong>10MB</strong> per domain (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#how_much_data_can_be_stored">source</a>). However, this limit varies between browsers and may be smaller on mobile devices.</li>
</ul>



<p>Additionally, performance considerations matter when using <code>dcc.Store</code> for large datasets. Each time a callback accesses or updates <code>dcc.Store</code>, Dash serializes and transmits the data over the network. If you store large JSON objects (for example, a DataFrame of thousands of rows), it can lead to significant bandwidth usage and slow performance, especially for users with limited internet speeds. In such cases, consider using server-side caching or database storage instead.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='how-to-debug-dcc-store'  id="boomdevs_7" class="wp-block-heading" >How to debug <code>dcc.Store</code></h2>



<p>Depending on the storage type, you can inspect, update, or delete storage values, which can be useful during development, especially for <code>session</code> and <code>local</code> types.</p>



<p>For this example, I will use a To-Do app built in Dash. You can find how I created it in this tutorial: <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">Build a To-Do app in Python with Dash (part 2/3)</a> and open the live app <a href="https://scripts.dash-resources.com/todo_app/app5.py/">here</a>.</p>



<ol class="wp-block-list">
<li>Open your browser’s Developer Tools (press <code>F12</code> or right-click on the page and select <strong>Inspect</strong>).</li>



<li>Navigate to <strong>Application</strong> (or a similar tab), where you can find Local Storage and Session Storage.</li>
</ol>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="1418" src="https://dash-resources.com/wp-content/uploads/2025/03/image-1.png" alt="" class="wp-image-794" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-1.png 2048w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-300x208.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-1024x709.png 1024w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-768x532.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-1536x1064.png 1536w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: an example of localStorage in my to-do app. We can see the exact content of two dcc.Store components: <code>current_index_memory</code> and <code>list_data_memory</code>. The other entries (<code>iconify-count</code> and <code>iconify-version</code>) are from a third-party library.</figcaption></figure>



<p>You will find the <em>Local Storage</em> and <em>Session Storage</em> list. However, store components using <code>storage_type="memory"</code> will not appear: the data is stored as a React state. It’s encapsulated in the JavaScript memory of the page and is not easily accessible. It’s also not particularly useful to access it directly.</p>



<p>When clicking, you can see the list of <code>dcc.Store</code> keys. Each store will have one entry for the content (JSON-serialized) and one entry for the <code>modified_timestamp</code>.</p>



<p>A few important notes:</p>



<ul class="wp-block-list">
<li><strong>Confidentiality:</strong> Notice how <strong>visible</strong> the information stored in a store component is to the end user! In the example above, the entire list of tasks is clearly visible. As a result, you should always remember <strong>not</strong> to store any confidential information in a <code>dcc.Store</code> component. This is especially true for <code>localStorage</code>, which can persist indefinitely (someone accessing your Dash app months later could see the same data as another user from months prior).</li>



<li><strong>Sharing storage:</strong> <code>localStorage</code> and <code>sessionStorage</code> are shared with other libraries in your app. So if you use third-party libraries, you may see their entries here. For example, I use a chatbot in one of my apps, and this chatbot extension stores its own info in <code>localStorage</code>.</li>
</ul>



<p class="callout"><strong>Pro-tip #1</strong>: When working with <code>storage_type="local"</code>, remember to delete your local storage entries when you need to simulate a fresh app initialization.<br><br><strong>Pro-tip #2</strong>: If you develop many apps at the same local URL (e.g., <code>http://127.0.0.1:8050/</code>), you can accidentally overwrite your local storage data across apps if you use the same store IDs. Keep this in mind to avoid unexpected bugs!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='some-best-practices-for-dcc-store'  id="boomdevs_8" class="wp-block-heading" >Some best practices for <code>dcc.Store</code></h2>



<h3 id='&#x27a1;-serialize-data-properly'  id="boomdevs_9" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serialize data properly</h3>



<p>A common error is attempting to store non-JSON-serializable objects, such as Pandas DataFrames, NumPy arrays, or custom Python objects. Since <code>dcc.Store</code> supports only JSON-compatible data, make sure to convert these objects first (using <code>.to_json()</code>, <code>.tolist()</code>, <code>json.dumps()</code>, etc.):</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><em>TypeError: Object of type DataFrame is not JSON serializable.</em></p>
</blockquote>



<p>If your data is not JSON serializable, it&#8217;s a strong indication that you should rely on an external source (e.g. a database). Note that for small images, you can still <a href="https://www.base64-image.de/">encode them in base64</a> to get a string. </p>



<h3 id='&#x27a1;-don-t-store-large-objects-or-dataframes'  id="boomdevs_10" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Don’t store large objects or DataFrames</h3>



<p>Avoid storing large datasets in <code>dcc.Store</code>. It’s better to use an external database and only store dataset IDs in the <code>dcc.Store</code> component.</p>



<p>There’s no hard limit for maximum size -it depends on your bandwidth and dataset. However, I would recommend keeping it under 10 MB for high-speed connections, 100KB-1MB for a production ready app.</p>



<p class="callout"><strong>Good to know:</strong> If you want to see a practical example of the impact it can have and how to debug performance issues with network development tools, see here: <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a></p>



<h3 id='&#x27a1;-use-the-right-storage-type'  id="boomdevs_11" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Use the right storage type</h3>



<p>Select storage type <code>memory</code>, <code>session</code>, or <code>local</code> based on how long the data should persist. If you don’t need any persistence, keep it light with <code>memory</code> or <code>session</code>. And remember that you have a limited amount of storage made possible by the browser.</p>



<h3 id='&#x27a1;-trigger-callbacks-efficiently'  id="boomdevs_12" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Trigger callbacks efficiently</h3>



<p>You can use the <code>modified_timestamp</code> property instead of <code>data</code> if you only need the store as a trigger (without the data itself). This reduces the data transmitted over the network. You can also use <code>modified_timestamp</code> to trigger a callback at initialization time. </p>



<p class="callout"><strong>Good to know: </strong>See an example of how you can use <code>modified_timestamp</code> instead of <code>data</code> here.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 id='conclusion'  id="boomdevs_13" class="wp-block-heading" >Conclusion</h1>



<p>I hope this article was helpful. My goal was to provide complementary information to the <a href="https://dash.plotly.com/dash-core-components/store">official documentation</a>! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p><code>dcc.Store</code> is an essential component that simplifies state management in Dash apps. By using it, you can avoid redundant computations, maintain user session data, and enhance performance. As your app grows, choosing the right storage type and structuring your callbacks efficiently will become increasingly important.</p>



<ul class="wp-block-list">
<li>If you want to learn more, take a look at the To-Do app tutorial, which provides a practical example of how to use <code>dcc.Store</code>: <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/">Build a To-Do app in Python with Dash (part 1/3)</a>.</li>



<li>If you have any questions, feel free to ask on the Plotly forum <a href="https://community.plotly.com/t/a-dcc-store-tutorial-how-it-works-storage-types-and-more/90866">here</a>.</li>
</ul>



<p>Happy coding <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" />!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="callout"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> <strong>Level up your Dash skills!</strong> <strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> I wrote a full course to help you <a href="https://dash-resources.com/dash-plotly-course/">learn how to think and design a Dash app with simple and advanced callbacks</a>. Real pro-tips, from a real human expert!</p>
<p>L’article <a href="https://dash-resources.com/understanding-dcc-store-in-dash-plotly/">Understanding dcc.Store in Dash Plotly</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Dash app callback performance: a real-world debugging example</title>
		<link>https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 20 Feb 2025 10:07:28 +0000</pubDate>
				<category><![CDATA[Experienced level]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=756</guid>

					<description><![CDATA[<p>While working on my app, I noticed a discrepancy in performance between the deployed version and my local environment. There was a little delay to [...]</p>
<p>L’article <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>While working on my app, I noticed a discrepancy in performance between the deployed version and my local environment. There was a little delay to display some information, that I found annoying.</p>



<p>In this blog post, I’ll walk you through how I quickly enhanced my app’s performance by analyzing Dash callback execution, which hopefully give you some ideas for your debugging too. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#context">Context</a>
      </li>
      <li>
        <a href="#so-how-to-debug-this">So how to debug this?</a>
      </li>
      <li>
        <a href="#resolution">Resolution</a>
      </li>
      <li>
        <a href="#result">Result</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<h2 id='context'  id="boomdevs_1" class="wp-block-heading" >Context</h2>



<p>My dash application is essentially a real estate map which displays information when clicking on map points.</p>



<p>The click that displayed information felt instant when running locally, but when running in production, it had about a one-second delay. I also noticed that the more elements I displayed on my map, the worse the performance became.</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/prod-app.mp4"></video><figcaption class="wp-element-caption">Illustration: you can see a 1-2sec delay between the click and the display of the information on the sidebar.</figcaption></figure>



<p>My production app and local app differ in some ways, and I could imagine at least two reasons:</p>



<ul class="wp-block-list">
<li><strong>Maybe callbacks are slower</strong> due to design differences between production and local development. For example, the cache used is not the same: I use Flask cache disk locally, which is faster, while my app uses a Redis backend to cache results. Redis introduces additional overhead due to the external request it must handle, compared to just RAM or disk caching on the same device.</li>



<li><strong>Maybe the request itself is slower</strong> due to upload/download speed limits. When deployed, my app is on a remote web server, whereas locally it runs directly on my computer. Obviously, this introduces extra overhead. It’s much faster for my browser to communicate with a local app on the same device as with a remote app over the internet.</li>
</ul>



<h2 id='so-how-to-debug-this'  id="boomdevs_2" class="wp-block-heading" >So how to debug this?</h2>



<p>To make it short, I tried to replicate the issue locally (which is always a good way to debug things). I started by inspecting the triggered callbacks after a click on the map. You might know that all Dash callbacks translate to HTTP requests, so I used the developer tools and network tab to analyze what was happening.</p>



<p class="callout">If you don’t know that callbacks translate to HTTP request, I recommend you to download my guide “<strong>Master Dash Callbacks: Architecture &amp; Best Practices (Free PDF)</strong>” <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> find it on the sidebar &#8212;-&gt; or below this article on mobile.</p>



<p>To simulate a “distant” request from my computer to a remote server, I set the network throttling to “Good 3G” to mimic a 3G mobile connection. <strong>This was key in debugging.</strong> Without this, everything appeared snappy in my browser.</p>



<p>I discovered that eight callbacks were triggered (directly or indirectly) after clicking on the map. Here is the debugging process in video:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/dev-debug.mp4"></video><figcaption class="wp-element-caption">Illustration: you can see no delay in debug mode. But when simulating network throttling, the delay became visible again.</figcaption></figure>



<p>As you can see in the video, <strong>it</strong> <strong>literally took around 14 seconds</strong> (!!) to complete the eight callbacks after clicking on the map.</p>



<p>Two requests appeared to be blocking the others, leading to huge delays: between 1 and 8 seconds for some callbacks. This is significant.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="501" src="https://dash-resources.com/wp-content/uploads/2025/02/image-9.png" alt="Illustration of the network debugging tools and the corresponding http requests to the callbacks." class="wp-image-759" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-9.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-300x73.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1024x251.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-768x188.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1536x376.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1320x323.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>When inspecting the first request, I quickly realized that the bottleneck was the time spent “Sending” the data: 4.05s. The “Waiting” time (when the browser waits for the server response) was only 64ms. So, the callback processing was not the problem—the data upload was.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="501" src="https://dash-resources.com/wp-content/uploads/2025/02/image-10.png" alt="Illustration of the timings for the first request." class="wp-image-760" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-10.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-300x73.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1024x251.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-768x188.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1536x376.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1320x323.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>When inspecting the second request, I saw that it was also blocked for 4.05s, meaning it was simply not triggered immediately.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="557" src="https://dash-resources.com/wp-content/uploads/2025/02/image-11.png" alt="Illustration of the timings for the second request." class="wp-image-761" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-11.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-300x82.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1024x279.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-768x209.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1536x418.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1320x359.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>The third request showed the same problem as the first one: too much time spent sending data, with only 108ms processing on the server side and no delay in receiving the result.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="555" src="https://dash-resources.com/wp-content/uploads/2025/02/image-12.png" alt="Illustration of the timings for the third request." class="wp-image-762" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-12.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-300x81.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1024x278.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-768x208.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1536x416.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1320x358.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>At this point, it was clear that two callbacks were problematic. To identify which callbacks these HTTP requests were triggering, I inspected the request details. The names of outputs, inputs, and states are visible as part of the information Dash sends with each callback:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="1048" src="https://dash-resources.com/wp-content/uploads/2025/02/image-13.png" alt="Illustration of debugging the inputs/states/outputs of a callback request" class="wp-image-763" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-13.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-300x154.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1024x524.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-768x393.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1536x786.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1320x675.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>I realized that the full map data (<code>map_figure_data</code>) was being sent. For my app, that meant all the points and their hover information (descriptions, images, etc.).</p>



<p>Even though it wasn’t a huge amount of data, it posed two problems:</p>



<ul class="wp-block-list">
<li>Upload speeds are usually slower than download speeds. For slow connections like mobile networks, this delay is unbearable.</li>



<li>The issue worsens as the number of data points on my map increases (!)</li>
</ul>



<p>The second callback had the same problem. This time, the map data was an input. The goal was to see how I could eliminate this <code>map_figure_data</code> dependency.</p>



<h2 id='resolution'  id="boomdevs_3" class="wp-block-heading" >Resolution</h2>



<p>I then looked at my two callbacks. Here’s what they originally looked like.</p>



<p><strong>callback 1 &#8211; before</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("map_selection_memory", "data"),
    [
        Input("map", "selectedData"),
        Input("location_data_memory", "data"),
        Input("layer_selection_memory", "data"),
    ],
    [
        State("map_curve_mapping_memory", "data"),
        State("map_selection_memory", "data"),
        State("map_figure_data", "data"),  # the problematic state
    ]
)
def store_map_selection(
    selected_data, location_data, layer, curve_mapping, selected_ids, figure_data
):
    """A callback that Stores the selected ids in memory."""
</code></pre>



<p>While reviewing the callback code, I realized that <code>figure_data</code> wasn’t even used. So, I removed it and it solved the problem for this callback… (yes, I guess that these things happens!).</p>



<p><strong>callback 1 &#8211; after (removed useless state!)</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("map_selection_memory", "data"),
    [
        Input("map", "selectedData"),
        Input("location_data_memory", "data"),
        Input("layer_selection_memory", "data"),
    ],
    [
        State("map_curve_mapping_memory", "data"),
        State("map_selection_memory", "data"),
        # just removed the useless state
    ]
)
def store_map_selection(
    selected_data, location_data, layer, curve_mapping, selected_ids
):
    """A callback that Stores the selected ids in memory."""
</code></pre>



<p>Now, looking at the second callback.</p>



<p><strong>callback 2 &#8211; before</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("cache_data_metadata_memory", "data"),
    [
        Input("map_figure_data", "data"),  # this is problematic
        Input("map_selection_memory", "data"),
        Input("data_modal_unchecked_memory", "data"),
    ],
    State("address_input_memory", "data"),
    State("layer_selection_memory", "data"),
    State("filters_selection_memory", "data"),
    prevent_initial_call=True,
)
def update_cache_data_metadata_memory(
    fig_data, selected_ids, unchecked_ids, address, layer, filters
):
    """Update metadata dict (...)"""
</code></pre>



<p>Again, <code>fig_data</code> wasn’t used. However, I still needed <code>map_figure_data</code> to trigger updates, so I changed the input property to <code>modified_timestamp</code>. This enables the callback to be triggered when <code>map_figure_data</code> changes (its timestamp is updated) while not transferring the whole data.</p>



<p><strong>callback 2 &#8211; after (changed to modified_timestamp)</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("cache_data_metadata_memory", "data"),
    [
        Input("map_figure_data", "modified_timestamp"),  # this is efficient
        Input("map_selection_memory", "data"),
        Input("data_modal_unchecked_memory", "data"),
    ],
    State("address_input_memory", "data"),
    State("layer_selection_memory", "data"),
    State("filters_selection_memory", "data"),
    prevent_initial_call=True,
)
def update_cache_data_metadata_memory(
    map_update_timestamp, selected_ids, unchecked_ids, address, layer, filters
):
    """Update metadata dict (...)"""
</code></pre>



<p>And that did the job perfectly!</p>



<p><strong>But what if I actually had to keep the full figure data</strong> ? I would have had two choices:</p>



<ul class="wp-block-list">
<li>Create another input, that is lighter than the figure data</li>



<li>Process the figure data in a clientside callback to avoid a roundtrip to the server.</li>
</ul>



<h2 id='result'  id="boomdevs_4" class="wp-block-heading" >Result</h2>



<p>These changes literally took 20 seconds to implement. The result was instantly better, even with the &#8220;Good 3G&#8221; mobile throttling still enabled:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/prod-app-fixed.mp4"></video><figcaption class="wp-element-caption">Illustration: no more delay on the debug app, even with 3G network throttling. </figcaption></figure>



<p>After making these adjustments, I ensured there were no other instances of <code>map_figure_data</code> being used as either an Input or Output.</p>



<h2 id='conclusion'  id="boomdevs_5" class="wp-block-heading" >Conclusion</h2>



<p>When debugging this issue, I thought it would be an interesting use case to share, because: </p>



<ul class="wp-block-list">
<li>Using <strong>developer tools</strong> is a powerful way to debug slow callbacks and sluggish Dash apps. Keep in mind that all inputs and states trigger data uploads, which in some cases can be extremely slow.</li>



<li>Using <strong>throttling mode</strong> allows you to simulate production conditions or at least non-optimal conditions, unlike debugging locally.</li>
</ul>



<p>I hope you found this interesting. Such performance explanations and debugging techniques are part of my <a href="https://dash-resources.com/dash-plotly-course/"><strong>Dash course</strong></a>, where you can find more <strong>detailed explanations and videos</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>If you have questions, feel free to ask them <a href="https://community.plotly.com/t/dash-app-callback-performance-a-real-world-debugging-example/90653">here</a> on Plotly&#8217;s forum.</p>



<p>Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/prod-app.mp4" length="378456" type="video/mp4" />
<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/dev-debug.mp4" length="1929505" type="video/mp4" />
<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/prod-app-fixed.mp4" length="785372" type="video/mp4" />

			</item>
		<item>
		<title>A guide to beautiful Dashboards (basic design principles)</title>
		<link>https://dash-resources.com/a-guide-to-beautiful-dashboards-basic-design-principles/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sun, 09 Feb 2025 15:26:47 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=707</guid>

					<description><![CDATA[<p>So you’ve started building dashboards with Dash Plotly. Bravo! But you soon realize that even if it is easy to build dashboards, it is somehow [...]</p>
<p>L’article <a href="https://dash-resources.com/a-guide-to-beautiful-dashboards-basic-design-principles/">A guide to beautiful Dashboards (basic design principles)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>So you’ve started building dashboards with Dash Plotly. Bravo! But you soon realize that even if it is easy to build dashboards, it is somehow <strong>not easy to make them look good</strong>. So how do you design a beautiful dashboard with Dash?</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#introduction">Introduction</a>
      </li>
      <li>
        <a href="#pick-a-font">Pick a font</a>
      </li>
      <li>
        <a href="#pick-colors-correctly">Pick colors correctly</a>
      </li>
      <li>
        <a href="#beware-of-the-hierarchy">Beware of the hierarchy</a>
      </li>
      <li>
        <a href="#space-things-out">Space things out!</a>
      </li>
      <li>
        <a href="#keep-things-homogeneous">Keep things homogeneous</a>
      </li>
      <li>
        <a href="#remember-to-style-charts-too">Remember to style charts too</a>
      </li>
      <li>
        <a href="#use-pre-made-components">Use pre-made components</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#see-also">See also:</a>
          </li>
        </ul>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>In this tutorial, I’ll give you <strong>tips</strong> and <strong>advices</strong> to keep in mind to create visually appealing dashboards and Dash apps, to increase the usability and aesthetics of your project.</p>



<h2 id='introduction'  id="boomdevs_1" class="wp-block-heading" >Introduction</h2>



<p>Let’s be honest: most dashboards are functional but completely fail in terms of UI/UX (user interface/user experience). And that’s normal—most of us are data analysts, data scientists, or Python developers, and UI/UX is not our job.</p>



<p>If you are building a dashboard for your own use or designed for technical people, you might not care about aesthetics. But if you want people outside your team to engage with and use the amazing dashboard that you made, you should invest time in making your dashboard visually appealing.</p>



<figure class="wp-block-image size-full is-style-default"><img loading="lazy" decoding="async" width="1378" height="1076" src="https://dash-resources.com/wp-content/uploads/2025/02/image.png" alt="Illustration: a dashboard with all possible visual problems." class="wp-image-708" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image.png 1378w, https://dash-resources.com/wp-content/uploads/2025/02/image-300x234.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-1024x800.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-768x600.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-1320x1031.png 1320w" sizes="auto, (max-width: 1378px) 100vw, 1378px" /><figcaption class="wp-element-caption">Can you even look at this dashboard ? It’s really hurting the eyes.</figcaption></figure>



<p>This is even more true if you are creating an app used by non-technical people (not in your field) or if you are developing a SaaS product for a broader audience with Dash.</p>



<p>Fortunately, by following a few rules, you can significantly improve your skills in building beautiful dashboards and apps. Let’s dive in. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='pick-a-font'  id="boomdevs_2" class="wp-block-heading" >Pick a font</h2>



<p>The harsh truth: the <strong>lack of a personalized font</strong> is one of the common traits of ugly dashboards. That’s why I put it at the top of this list. You’ll never see a professional-looking app that sticks to &#8220;<em>Times New Roman</em>&#8221; (i.e., the default font in most web browsers).</p>



<p>This doesn’t mean this font should never be used, but it means that default fonts quickly show that no effort was put into aesthetics. So, setting a proper font is a real quick win.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1382" height="1063" src="https://dash-resources.com/wp-content/uploads/2025/02/image-1.png" alt="Illustration: the same dashboard with fonts fixed." class="wp-image-709" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-1.png 1382w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-300x231.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-1024x788.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-768x591.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-1320x1015.png 1320w" sizes="auto, (max-width: 1382px) 100vw, 1382px" /><figcaption class="wp-element-caption">The same dashboard, but we changed font: it looks instantly different.</figcaption></figure>



<p>Serif fonts often imply tradition and formality (<em>Times New Roman</em> is widely used in newspapers and books, and there’s nothing wrong with that if your app is focused on reports, articles, or anything content-heavy). But usually, dashboards benefit from sans-serif fonts for a cleaner, more modern look.</p>



<p>Recommended fonts are the basic ones:</p>



<ul class="wp-block-list">
<li><em>Arial, Open Sans, Verdana, Helvetica Neue</em>.</li>
</ul>



<p>If you need more stylish and trendy fonts, here are some great options:</p>



<ul class="wp-block-list">
<li><em>Roboto</em> (modern and widely used in web design)</li>



<li><em>Lato</em> (clean and friendly, great for dashboards)</li>



<li><em>Montserrat</em> (sleek and professional)</li>



<li><em>Inter</em> (highly readable and trendy for UI design)</li>
</ul>



<p>If you need more inspiration, you can explore <a href="https://fonts.google.com/">Google Fonts</a> or <a href="https://fonts.adobe.com/">Adobe Fonts</a> to find stylish options that fit your dashboard’s aesthetic. But being too exotic is not a good idea either.</p>



<p class="callout"><strong>Note:</strong> You should always set a font because otherwise, your app might look different on various devices (Windows, Mac, Linux, etc.) and browsers, which may pick different default fonts.</p>



<h2 id='pick-colors-correctly'  id="boomdevs_3" class="wp-block-heading" >Pick colors correctly</h2>



<p><strong>No colors is terrible</strong></p>



<p>Colors help with contrast, and contrast is absolutely necessary if you want to ease the eye. A dashboard with no contrast is hard to read, making users tire quickly. If everything looks the same, users won’t know where to focus.</p>



<p>Using just black and white or a single muted color can make your app look dull and unstructured. Instead, introduce some contrast using different shades and tones, but in a subtle way. Use colors to differentiate sections, highlight key metrics, and guide user attention effortlessly.</p>



<p><strong>Too many colors is terrible</strong></p>



<p>On the flip side, throwing in too many colors makes everything look chaotic. Too much contrast can make your dashboard overwhelming and confusing. A rainbow-colored dashboard is not visually appealing—it’s distracting.</p>



<p>Go easy on gradients. Too many gradients will make your app look like outdated WordArt! Use soft gradients sparingly—to highlight a few buttons or text, but not everywhere. Stick to a defined color palette and ensure your colors complement each other.</p>



<p><strong>Importance of color choice</strong></p>



<p>Colors enhance the appearance of your app but also improve user experience: you can emphasize the importance of a button by its color alone, meaning the user instantly understands its significance.</p>



<p>People also associate certain colors with specific actions—red buttons usually indicate critical actions, while blue or black buttons tend to be more neutral. Green often signals success, and yellow can be used for warnings.</p>



<p>Just like fonts, using default colors (100% red, 100% green, or 100% blue) will make your dashboard look unpolished. Instead, pick variants of these colors—softer shades, deeper tones, or slight variations.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="1083" src="https://dash-resources.com/wp-content/uploads/2025/02/image-2.png" alt="Illustration: the same dashboard with better colors." class="wp-image-710" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-2.png 1376w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-300x236.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-1024x806.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-768x604.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-1320x1039.png 1320w" sizes="auto, (max-width: 1376px) 100vw, 1376px" /><figcaption class="wp-element-caption">Our dashboard after changing the colors for softer colors. We also got rid of contrasts problems like a dark text on a dark button and removed old-looking gradients.</figcaption></figure>



<p class="callout"><strong>Pro tip:</strong> If you don’t know what colors to use, you can rely on predefined color palettes from <a href="https://mantine.dev/theming/colors/">Mantine</a>, <a href="https://getbootstrap.com/docs/4.0/utilities/colors/">Bootstrap</a>, or websites like <a href="https://flatuicolors.com/">Flat UI Colors</a>.</p>



<p>Also remember that users have different visual impairments (e.g., color blindness). Learn more about it here: <a href="https://davidmathlogic.com/colorblind/">coloring for colorblindness</a>.</p>



<h2 id='beware-of-the-hierarchy'  id="boomdevs_4" class="wp-block-heading" >Beware of the hierarchy</h2>



<p>Hierarchy isn&#8217;t just about making titles bigger —it&#8217;s about creating a <strong>natural flow for the eyes</strong>. When someone looks at your dashboard, they should naturally understand what&#8217;s important and what&#8217;s secondary.</p>



<p>A simple but effective approach is to stick to 3-4 text sizes for your entire app. Your main title should be the biggest (around 32px), followed by section titles (24px), and then your regular text (14-16px). Don&#8217;t go smaller than 12px &#8211; nobody likes squinting at tiny text!</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1384" height="1118" src="https://dash-resources.com/wp-content/uploads/2025/02/image-3.png" alt="Illustration: the same dashboard with better hierarchy." class="wp-image-712" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-3.png 1384w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-300x242.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-1024x827.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-768x620.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-1320x1066.png 1320w" sizes="auto, (max-width: 1384px) 100vw, 1384px" /><figcaption class="wp-element-caption">Now titles are bigger, as well as the most important data (sales, revenue, profit). The “Recent activity” section is larger and the text below is smaller.</figcaption></figure>



<p>But size isn&#8217;t everything. You can also create hierarchy through weight (bold vs regular) and color intensity. Just like in a newspaper, the most important stuff should catch your eye first.</p>



<p>Think about it: when you look at a well-designed website, you instantly know where to focus, right? That&#8217;s good hierarchy at work.</p>



<h2 id='space-things-out'  id="boomdevs_5" class="wp-block-heading" >Space things out!</h2>



<p>Let your components breathe:</p>



<ul class="wp-block-list">
<li>Add margins around text and padding inside buttons.</li>



<li>Text should not be stuck to the border of its container—use proper padding and margins.</li>
</ul>



<p>The easiest way to handle spacing is to pick a basic unit (like 8 pixels) and use multiples of it. Put a little space between related items (8px), more space between different elements (16px), and even more space between major sections (24px or 32px).</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1385" height="1637" src="https://dash-resources.com/wp-content/uploads/2025/02/image-4.png" alt="Illustration: the same dashboard with better spacing." class="wp-image-713" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-4.png 1385w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-254x300.png 254w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-866x1024.png 866w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-768x908.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-1300x1536.png 1300w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-1320x1560.png 1320w" sizes="auto, (max-width: 1385px) 100vw, 1385px" /><figcaption class="wp-element-caption">Now we can breathe! We added space between each section along with borders. We also fix alignment of the info icon, of the logout button and of the activities. The main buttons are spaced gracefully too, as well as the main data.</figcaption></figure>



<p>Remember: <strong>white space isn&#8217;t wasted space</strong>. It&#8217;s like the margins in a book &#8211; without them, everything would be a mess and hard to read. Give your components some breathing room. Your users&#8217; eyes will thank you.</p>



<h2 id='keep-things-homogeneous'  id="boomdevs_6" class="wp-block-heading" >Keep things homogeneous</h2>



<p>In art, <strong>proportions</strong> are an integral part of the works of the greatest masters. Many sculptures and paintings, for example, use the <a href="https://en.wikipedia.org/wiki/Golden_ratio">golden ratio</a>.</p>



<p>The same applies to interfaces: consistency is key to making dashboards look polished and professional.</p>



<p>But what does this mean in practice?</p>



<ul class="wp-block-list">
<li>Apply the same margins and padding throughout, ensuring a balanced layout while respecting hierarchy.</li>



<li>Stick to 2-3 main colors with one accent color for emphasis. Example: if you use blue buttons, don’t randomly switch to green unless it serves a purpose.</li>



<li>Keep shapes and styles uniform—rounded buttons should stay rounded, and shadows should be consistent across cards.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1385" height="1637" src="https://dash-resources.com/wp-content/uploads/2025/02/image-5.png" alt="Illustration: the same dashboard with better proportions." class="wp-image-714" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-5.png 1385w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-254x300.png 254w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-866x1024.png 866w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-768x908.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-1300x1536.png 1300w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-1320x1560.png 1320w" sizes="auto, (max-width: 1385px) 100vw, 1385px" /><figcaption class="wp-element-caption">We homogenized the buttons, so that only the most important (export data) stands out. By their size and color, the user now guesses instantly what are the most importants buttons.</figcaption></figure>



<p>Small details make a big difference!</p>



<h2 id='remember-to-style-charts-too'  id="boomdevs_7" class="wp-block-heading" >Remember to style charts too</h2>



<p>By default, Plotly charts do not inherit the styles of your app. So, be sure to at least set the font style and colors to match your dashboard.</p>



<p>Also, tweak chart elements like gridlines, legend placement, and axis labels to keep the same spacing you used before (to keep things homogeneous!).</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1391" height="1755" src="https://dash-resources.com/wp-content/uploads/2025/02/image-7.png" alt="Illustration: the same dashboard with better chart styling." class="wp-image-716" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-7.png 1391w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-238x300.png 238w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-812x1024.png 812w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-768x969.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-1217x1536.png 1217w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-1320x1665.png 1320w" sizes="auto, (max-width: 1391px) 100vw, 1391px" /><figcaption class="wp-element-caption">We changed the color and the font familiy of the plotly chart to use the same as the rest of the dashboard. You will often need to hardcode the values in Python, in the figure configuration / layout parameters.</figcaption></figure>



<p class="callout">Another <strong>pro tip</strong>: If your dashboard has multiple charts, keep them consistent in terms of styling. Same font, same color scheme, same layout rules—it all helps with clarity and makes your dashboard look like a professionally designed tool.</p>



<h2 id='use-pre-made-components'  id="boomdevs_8" class="wp-block-heading" >Use pre-made components</h2>



<p>Last but not least: you should use component libraries like <a href="https://dash-bootstrap-components.opensource.faculty.ai/">Dash Bootstrap Components</a> (DBC) or <a href="https://www.dash-mantine-components.com/">Dash Mantine Components</a> (DMC). These libraries come with a lot of default CSS rules and choices, a technic known as a <a href="https://meyerweb.com/eric/tools/css/reset/">CSS reset</a>.</p>



<p>These rules ensure that HTML elements are correctly placed, well-spaced, have the right font sizes, and maintain a consistent, homogeneous style. Exactly everything we did before, so it will make you gain time.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1377" height="2113" src="https://dash-resources.com/wp-content/uploads/2025/02/image-8.png" alt="Illustration: the same dashboard with pre made component library." class="wp-image-717" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-8.png 1377w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-196x300.png 196w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-667x1024.png 667w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-768x1178.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-1001x1536.png 1001w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-1335x2048.png 1335w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-1320x2026.png 1320w" sizes="auto, (max-width: 1377px) 100vw, 1377px" /><figcaption class="wp-element-caption">We applied the Mantine components styles to our dashboard: buttons are different, and containers have some remarkable shadow. The good thing is that we don’t need all the previous steps to get to this one.</figcaption></figure>



<p>However, these component libraries are popular which is a good and a bad thing:</p>



<ul class="wp-block-list">
<li>The good: <strong>people like them</strong>. Remember, users don’t want anything too crazy—they prefer familiar, intuitive designs. They will like your design because they are used to it.</li>



<li>The bad: <strong>all websites look the same</strong>. However, with the choices you have (layout, colors, fonts, etc.), you can still make your dashboard unique while keeping it aesthetically pleasant.</li>



<li></li>
</ul>



<p class="callout"><strong>My 2 cents:</strong> I always use at least DBC or Mantine in my Dash projects, whether I create custom components or not, because they bring a CSS reset and a lot of CSS utils.</p>



<h3 id='see-also'  id="boomdevs_9" class="wp-block-heading" >See also:</h3>



<ul class="wp-block-list">
<li><a href="https://dash-resources.com/writing-in-process/">How to use Dash Bootstrap Components (soon)</a></li>



<li><a href="https://dash-resources.com/writing-in-process/?article=dash-mantine-components-tutorial">How to use Dash Mantine Components (soon)</a></li>
</ul>



<h2 id='conclusion'  id="boomdevs_10" class="wp-block-heading" >Conclusion</h2>



<p>Let&#8217;s wrap this up with an important reminder: <strong>these aren&#8217;t strict commandments set in stone</strong>. Think of them more as helpful guidelines that, when mixed and matched thoughtfully, can lead to visually appealing dashboards. You don&#8217;t need to follow every single rule.</p>



<p>The best way to improve your design skills is to <strong>experiment and stay observant</strong>. Pay attention to the apps and tools you enjoy using. What makes them pleasant to work with? How do they handle fonts, colors, and spacing? <strong>Take inspiration</strong> from what works well in your favorite applications.</p>



<p>And here&#8217;s a final thought: people tend to find &#8220;normal&#8221; things beautiful.</p>



<ul class="wp-block-list">
<li><strong>You don&#8217;t need to reinvent the wheel </strong>or create the most unique dashboard ever seen. Aim for clean, familiar, and professional rather than wildly original.</li>



<li>A “dash” <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f643.png" alt="🙃" class="wp-smiley" style="height: 1em; max-height: 1em;" /> of creativity is great, but remember that if your dashboard <strong>looks too exotic</strong>, it might end up being harder to use!</li>
</ul>



<p>The goal is simply to create something that looks professional and is pleasant to use.</p>



<p>Follow these guidelines, trust your eye, and you&#8217;ll be well on your way to creating dashboards that both you and your users will enjoy.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>I hope you liked this guide and learn. This is part of my <a href="https://dash-resources.com/dash-plotly-course/">Dash plotly Course</a>, be sure to check it out if you want to learn more about building dashboards. </p>



<p>If you have any question, join us on the dedicated topic on Plotly’s forum: <a href="https://community.plotly.com/t/a-guide-to-beautiful-dashboards-basic-design-principles-dash-resources-com/90495/4">here</a>. </p>
<p>L’article <a href="https://dash-resources.com/a-guide-to-beautiful-dashboards-basic-design-principles/">A guide to beautiful Dashboards (basic design principles)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Build a chatbot web app under 5min in Python</title>
		<link>https://dash-resources.com/build-a-chatbot-web-app-under-5min-in-python/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sat, 25 Jan 2025 15:45:44 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=576</guid>

					<description><![CDATA[<p>In this tutorial, we&#8217;ll build a ChatGPT-like web application using Dash and OpenAI&#8217;s GPT models. We will create a simple but powerful interface that allows [...]</p>
<p>L’article <a href="https://dash-resources.com/build-a-chatbot-web-app-under-5min-in-python/">Build a chatbot web app under 5min in Python</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we&#8217;ll build a ChatGPT-like web application using Dash and OpenAI&#8217;s GPT models. We will create a simple but powerful interface that allows users to interact with an AI assistant, with persistent message storage using browser local storage.</p>



<p>You&#8217;ll find below a <a href="#app-video">demo video</a> of the app.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#prerequisites">Prerequisites</a>
      </li>
      <li>
        <a href="#step-1-setting-up-the-environment">Step 1: Setting up the Environment</a>
      </li>
      <li>
        <a href="#step-2-creating-the-application-layout">Step 2: Creating the application layout</a>
      </li>
      <li>
        <a href="#step-3-implementing-the-chat-callback">Step 3: Implementing the chat callback</a>
      </li>
      <li>
        <a href="#step-4-running-the-app">Step 4: Running the app</a>
      </li>
      <li>
        <a href="#full-code">Full code</a>
      </li>
      <li>
        <a href="#going-further">Going further</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let’s get started!</p>



<h2 id='prerequisites'  id="boomdevs_1" class="wp-block-heading" >Prerequisites</h2>



<p>Before we begin, make sure you have Python 3.9 or later installed. We then need the following:</p>



<ul class="wp-block-list">
<li><em>Dash Plotly</em>: the best Python framework for building interactive web apps and dashboards;</li>



<li><em>Dash-chat component</em>: a community-supported tool for creating chat user interfaces (UI);</li>



<li><em>OpenAI package</em>: used to retrieve responses from OpenAI GPT models.</li>
</ul>



<p class="callout"><strong>Important</strong> : you&#8217;ll need an OpenAI API key to run the following code. You can get one from the <a href="https://platform.openai.com/">OpenAI platform</a>.</p>



<h2 id='step-1-setting-up-the-environment'  id="boomdevs_2" class="wp-block-heading" >Step 1: Setting up the Environment</h2>



<p>First, let’s install the packages using <code>pip</code>:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash">pip install dash dash-chat openai</code></pre>



<p>Then, let&#8217;s import our required packages and set up our OpenAI client:</p>



<pre class="wp-block-code"><code lang="Python" class="language-Python">import os
import dash
from dash import callback, html, Input, Output, State, dcc
from dash_chat import ChatComponent
from openai import OpenAI

# Initialize OpenAI client
api_key = os.environ.get("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)</code></pre>



<p>Note: We&#8217;re getting the API key from environment variables for security. It’s a best practice not to hardcode keys in your code to avoid sharing it by mistaking.</p>



<h2 id='step-2-creating-the-application-layout'  id="boomdevs_3" class="wp-block-heading" >Step 2: Creating the application layout</h2>



<p>Next, we&#8217;ll create our Dash application and define its layout:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">app = dash.Dash(__name__)

# Define default messages for the chat
default_messages = [
    {"role": "assistant", "content": "Hello!"},
]

app.layout = html.Div(
    [
        ChatComponent(
            id="chat-component",
            messages=[]  # Initialize empty since we'll load from storage
        ),
        
        # The store component help use save messages
        dcc.Store("chat-memory", data=default_messages, storage_type="local"),
    ],
    
    # Some basic CSS styling for the app
    style={
        "max-width": "800px",
        "margin": "0 auto",
        "font-family": "Arial, sans-serif",
        "padding": "20px",
    }
)
</code></pre>



<p>The Dash Chat component follows the same structure as the OpenAI conversation API with <code>role</code> and <code>content</code>. The AI messages will have <code>role="assistant"</code> while the user messages will have <code>role="user"</code>.</p>



<p>Instead of passing the messages directly to the <code>ChatComponent</code>, we save them into a <code>dcc.Store</code> component. This component will enable us to persist the messages in the browser’s session: the discussion will still show up if we reload the page, but will be cleared if we close the tab/browser.</p>



<p class="callout"><strong>Good to know</strong>: setting storage type to &#8220;local&#8221; enable saving the discussion even when the browser is closed. You might want to create a reset button if you do so!</p>



<p>We also added basic CSS styling for the app. In Dash, you can either add styling with the <code>style</code> parameters on most components or include your styles in an external stylesheet (see more).</p>



<h2 id='step-3-implementing-the-chat-callback'  id="boomdevs_4" class="wp-block-heading" >Step 3: Implementing the chat callback</h2>



<p>Callbacks make Dash applications interactive by linking <code>Input</code> components (user actions) to <code>Output</code> components (updates in the app) while handling logic on the server:</p>



<ul class="wp-block-list">
<li><strong>Input</strong> triggers a callback based on user actions, such as typing a message in the chat.</li>



<li><strong>Output</strong> defines what part of the app updates, like refreshing the chat interface with new messages.</li>



<li><strong>State</strong> retrieves current values without triggering a callback, useful for accessing stored data like chat history.</li>
</ul>



<p>Here’s how it works:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("chat-component", "messages"),
    Output("chat-memory", "data"),
    Input("chat-component", "new_message"),
    State("chat-memory", "data")
)
def handle_chat(new_message, messages):
    # If new_message is None, just return the stored messages
    # This is run at page load
    if not new_message:
        return messages, messages

    # If we have a user message, concatenate it to the list of messages
    updated_messages = messages + [new_message]

    # If the new message comes from the user, trigger the OpenAI API
    if new_message["role"] == "user":
        # We use the OpenAI completion API to get an answer to the user message
        response = client.chat.completions.create(
            model="gpt-4",
            messages=updated_messages,
        )
        bot_response = {
            "role": "assistant",
            "content": response.choices[0].message.content.strip()
        }

        # Append the new message to the message list
        updated_messages += [bot_response]

    # We update both the chat component and the chat memory
    return updated_messages, updated_messages
</code></pre>



<p>In this case, our input is the chat user input, provided by dash-chat component. The processing achieved by the callback will involve retrieving an answer from the OpenAI API, and the output is the updated list of messages.</p>



<p>Straightforward and simple <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="callout"><strong>Learn more</strong> on callbacks with this tutorial: <a href="https://dash-resources.com/tutorial-dash-callbacks-schemas-examples/">Dash callbacks, schemas and examples.</a></p>



<h2 id='step-4-running-the-app'  id="boomdevs_5" class="wp-block-heading" >Step 4: Running the app</h2>



<p>We finally add the code to run our server:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">if __name__ == "__main__":
    app.run_server(debug=True)
</code></pre>



<p>Here is the result:</p>



<figure id="app-video" class="wp-block-video"><video height="1124" style="aspect-ratio: 1442 / 1124;" width="1442" controls src="https://dash-resources.com/wp-content/uploads/2025/01/dash-chat.mp4"></video></figure>



<p>You can try reloading the page; you’ll see the chat output is kept. But if you close the tab, a new session will be created next time.</p>



<h2 id='full-code'  id="boomdevs_6" class="wp-block-heading" >Full code</h2>



<p>You can download the full code below, with guided instructions on how to run the app:</p>



                <div class="ml-embedded" data-form="FTXAyP"></div>
            



<h2 id='going-further'  id="boomdevs_7" class="wp-block-heading" >Going further</h2>



<p>Here are a few ideas to improve this chat app:</p>



<ul class="wp-block-list">
<li>add a &#8220;clear chat&#8221; button that triggers a callback which reset the messages sent ;</li>



<li>add an input to join a file, so that the AI can answer based on content ;</li>



<li>save messages in a proper database instead of browser storage.</li>
</ul>



<p>The Dash Chat component is a recent work in progress, so be sure to check out the coming updates <a href="https://community.plotly.com/t/dash-chat-component/89514">here</a>.</p>



<h2 id='conclusion'  id="boomdevs_8" class="wp-block-heading" >Conclusion</h2>



<p>I hope this short tutorial convinced you how easy and beginner-friendly it is to build a chatbot app with Dash plotly! With just a few lines of Python, you can create an interactive, AI-powered chat interface that works seamlessly in the browser.</p>



<p>Dash plotly can be used for both small data apps, AI apps, dashboard or real SaaS applications. It makes it easy to connect frontend and backend with pure Python. Check-out all the examples on plotly’s website: <a href="https://plotly.com/examples/">https://plotly.com/examples/</a></p>



<p>If you want more inspiration, here are a few other apps:</p>



<ul class="wp-block-list">
<li><a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/">A classic Todo app</a>: A simple yet powerful app to manage your daily tasks, showcasing Dash’s flexibility for non-AI use cases.</li>



<li><a href="https://quizdash.onrender.com">QuizDash</a>: A fun app that generates quizzes on any topic using the OpenAI API.</li>



<li><a href="https://stocktistics.com/stocksaavy">StockSaavy</a>: An AI powered chat-agent that answers questions about recent news articles pertaining to stocks in the S&amp;P500.</li>
</ul>



<p>Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/build-a-chatbot-web-app-under-5min-in-python/">Build a chatbot web app under 5min in Python</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/01/dash-chat.mp4" length="1513612" type="video/mp4" />

			</item>
		<item>
		<title>Build a To-Do app in Python with Dash (part 2/3)</title>
		<link>https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Wed, 08 Jan 2025 19:05:17 +0000</pubDate>
				<category><![CDATA[Intermediate level]]></category>
		<category><![CDATA[pattern-matching]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=434</guid>

					<description><![CDATA[<p>In part 1, we built a basic To-Do application with Dash and Dash Mantine Components (DMC). We created a single task list where users could [...]</p>
<p>L’article <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">Build a To-Do app in Python with Dash (part 2/3)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/">part 1</a>, we built a basic To-Do application with Dash and Dash Mantine Components (DMC). We created a single task list where users could add, modify, and delete tasks. However, our app had two major limitations:</p>



<ol class="wp-block-list">
<li>Tasks are lost when the page reloads</li>



<li>Users can only manage one list at a time</li>
</ol>



<p>In this tutorial, we&#8217;ll solve these problems by making tasks persistent using browser storage, and supporting multiple task lists by modifying the current callbacks.</p>



<p>Here&#8217;s a live display of the app you&#8217;ll learn to build (or <a href="https://scripts.dash-resources.com/todo_app/app5.py/">click here</a>):</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/todo_app/app5.py/" width="100%" height="500" frameBorder="0"></iframe>



<p>Let&#8217;s dive in!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='part-1-making-tasks-persistent'  id="boomdevs_1" class="wp-block-heading" >Part 1: Making tasks persistent</h2>



<p>When we reload our current app, all tasks are reset to their initial state. This happens because Dash reinitializes all variables when the page reloads. To fix this, we need a way to save our tasks somewhere.</p>



<p>There are several options for persistence:</p>



<ul class="wp-block-list">
<li>Database (SQL, MongoDB, etc.)</li>



<li>File system</li>



<li>Browser storage</li>
</ul>



<p>For simplicity, we&#8217;ll use browser storage through Dash&#8217;s built-in <code>dcc.Store</code> component. This component can save data in the browser&#8217;s:</p>



<ul class="wp-block-list">
<li><code>memory</code>: Data is lost on page refresh</li>



<li><code>session</code>: Data persists until the browser tab is closed</li>



<li><code>local</code>: Data persists even after closing the browser</li>
</ul>



<p>As we want to keep tasks over the time,  <code>local</code> seems a perfect fit.</p>



<p class="callout"><strong>Note</strong>: using <code>local</code> means that the information is stored in the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage">localStorage</a> of the browser. It will stay as long as the user do not clean its browser / reset history.</p>



<h3 id='1-adding-storage-component'  id="boomdevs_2" class="wp-block-heading" >1. Adding storage component</h3>



<p>First, let&#8217;s modify our app to use <code>dcc.Store</code>. We&#8217;ll move our task list data into the store:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">app.layout = dmc.MantineProvider(
    [
        dmc.Container(
            get_list_layout(),  # No data passed here anymore
            size=400,
        ),
        dcc.Store("list_data_memory", data=sample_list_data, storage_type="local"),
        dcc.Store("current_index_memory", data=sample_list_data[0]["index"], storage_type="local"),
    ]
)
</code></pre>



<p>We wrap our layout in a list to include both the container and store.</p>



<p>The function<code>get_list_layout()</code> no longer receives data directly. Instead, a new callback will load the data from the <code>list_data_memory</code> directly to the components that need to be updated (list’ title, tasks, …).</p>



<p>Finally, we also add a <code>current_index_memory</code> to save the index of the currently displayed list.</p>



<h3 id='2-updating-the-layout-functions'  id="boomdevs_3" class="wp-block-heading" >2. Updating the layout functions</h3>



<p>Our <code>get_list_layout()</code> function needs to change since it won&#8217;t receive data directly:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_list_layout():
    """ Returns the list of checkboxes """

    content = dmc.Paper(
        [
            dmc.Title(id="main_list_title", order=2),

            dmc.Container(
                id="main_task_container",
                px=0,
                mt="md",
                mb="md",
            ),

            dmc.Button(
                "Add a new task",
                id="new_task_button",
                style={"width": "100%"},
                variant="outline",
                color="gray",
            )
        ],
        shadow="sm",
        p="md",
        mt="md",
        radius="sm",
    )

    return content
</code></pre>



<p>As you can see, we removed the <code>list_data</code> parameter. Instead of populating the layout with data, we add IDs to the title and task container. We will populate them using callbacks reading from <code>list_data_memory</code>.</p>



<h3 id='3-adding-update-callback'  id="boomdevs_4" class="wp-block-heading" >3. Adding update callback</h3>



<p>Let’s build a callback to update our container when the stored data changes:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("main_task_container", "children"),
    Output("main_list_title", "children"),
    Input("list_data_memory", "data"),
)
def update_task_container(list_data):
    """ Updates the list of tasks and list title"""

    return get_tasks_layout(list_data["tasks_list"]), list_data["title"]
</code></pre>



<p>This callback listens for changes in our stored data and updates both the task list and list title. that way, we’re sure that the layout will be updated every time when <code>list_data_memory</code> changes.</p>



<p>We set the <code>prevent_initial_call=False</code> (implicity) because we actually want this callback to populate the layout using the stored data from local storage at loading time.</p>



<h3 id='4-modifying-task-callbacks'  id="boomdevs_5" class="wp-block-heading" >4. Modifying task callbacks</h3>



<p>Now we need to update our task-related callbacks to work with stored data. Instead of directly modifying the layout, they&#8217;ll update the stored data:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("list_data_memory", "data", allow_duplicate=True),
    Input("new_task_button", "n_clicks"),
    State("list_data_memory", "data"),
    prevent_initial_call=True,
)
def add_task(n_clicks, list_data):
    """ Adds a task to the list """
    if not n_clicks:
        raise PreventUpdate

    # Create new task dictionary
    new_index = uuid.uuid4().hex
    new_task = {
        "index": new_index,
        "content": "",
        "checked": False,
    }

    # Add new task to the tasks list in memory
    list_data["tasks_list"].append(new_task)
    return list_data
</code></pre>



<p>The main changes are:</p>



<ol class="wp-block-list">
<li>Output is now the store data instead of the container</li>



<li>We take the current store data as State</li>



<li>We modify and return the stored data</li>
</ol>



<p>We&#8217;ll do the same for the remove task callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("list_data_memory", "data", allow_duplicate=True),
    Input({"type": "task_del", "index": ALL}, "n_clicks"),
    State("list_data_memory", "data"),
    prevent_initial_call=True,
)
def remove_task(n_clicks, list_data):
    """ Remove a task from the list """
    if not any(n_clicks):
        raise PreventUpdate

    task_index = ctx.triggered_id["index"]

    # Find and remove the task with matching index
    list_data["tasks_list"] = [
        task for task in list_data["tasks_list"]
        if task["index"] != task_index
    ]

    return list_data
</code></pre>



<p>Apart from the fact that we can persist the data, this way of updating a <code>dcc.Store</code> is a better, cleaner way than relying directly on the container. We have more control on the stored data, on its form and when it is updated.</p>



<h3 id='5-adding-task-update-callback'  id="boomdevs_6" class="wp-block-heading" >5. Adding task update callback</h3>



<p>We also need to handle updating task content and checked status:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("list_data_memory", "data", allow_duplicate=True),
    Input({"type": "task_checked", "index": ALL}, "checked"),
    Input({"type": "task_content", "index": ALL}, "value"),
    State("list_data_memory", "data"),
    prevent_initial_call=True,
)
def update_task_checked(checked_values, content_values, list_data):
    """Updates the checked state and content of tasks"""
    if not checked_values:
        raise PreventUpdate

    # Find the index position in our list of tasks
    task_index = ctx.triggered_id["index"]
    task_pos = [task["index"] for task in list_data["tasks_list"]].index(task_index)

    task_checked_value = checked_values[task_pos]
    task_content_value = content_values[task_pos]

    # Update the task values in list_data
    list_data["tasks_list"][task_pos]["checked"] = task_checked_value
    list_data["tasks_list"][task_pos]["content"] = task_content_value

    return list_data
</code></pre>



<h3 id='6-run-the-app'  id="boomdevs_7" class="wp-block-heading" >6. Run the app</h3>



<p>Now our tasks persist across page reloads! You can try it by adding some tasks and reloading this page, you tasks should still be here <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/todo_app/app3.py/" width="100%" height="500" frameBorder="0"></iframe>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/Spriteware/todo-app-python-dash/blob/master/app3.py">See the full code on Github</a> or <a href="https://scripts.dash-resources.com/todo_app/app3.py/">open the app</a>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='part-2-supporting-multiple-lists'  id="boomdevs_8" class="wp-block-heading" >Part 2: Supporting multiple lists</h2>



<p>You will be surprised how easy it is now to handle the multiple list. As we have a <em>store</em> <em>→</em> <em>layout</em> scheme, we only need to modify the way that the data is structured and modify the layout to add this feature.</p>



<h3 id='1-updated-data-structure'  id="boomdevs_9" class="wp-block-heading" >1. Updated data structure</h3>



<p>First, let&#8217;s change our data structure to support multiple lists:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">sample_list_data = [
    {
        "index": uuid.uuid4().hex,
        "title": "My Tasks",
        "tasks_list": [
            {
                "index": uuid.uuid4().hex,
                "content": "Task A",
                "checked": True,
            },
            # ... other tasks
        ],
    },
    {
        "index": uuid.uuid4().hex,
        "title": "Shopping list",
        "tasks_list": [],
    }
]
</code></pre>



<p>As you can see, we simply wrap the old <code>dict</code> inside a list, and add another example. We also anticipate and add a unique ID (<code>index</code>) because soon or later we will need to identify lists from each other.</p>



<h3 id='2-adding-list-navigation'  id="boomdevs_10" class="wp-block-heading" >2. Adding list navigation</h3>



<p>Let’s add a sidebar for list navigation. Each list will be represented as a card with a title and a progression bar.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="540" src="https://dash-resources.com/wp-content/uploads/2025/01/image-2-1024x540.png" alt="Illustration of the navigation sidebar with title and progress bar for each task list." class="wp-image-436" style="width:699px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-2-1024x540.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-2-300x158.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-2-768x405.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-2-1536x810.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-2-1320x696.png 1320w, https://dash-resources.com/wp-content/uploads/2025/01/image-2.png 1628w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Illustration of the navigation sidebar with title and progress bar for each task list.</figcaption></figure>
</div>


<p>A new function <code>get_list_navigation_layout</code> will handle this:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_list_navigation_layout(list_data, current_index):
    """ Returns a list of lists titles and progressions """

    items = []

    for list_item in list_data:
        # Compute progression
        progress_value = get_progression(list_item)
        progress_color = "green" if progress_value == 100 else "blue"

        # Build card element
        elem = dmc.Paper(
            html.A(
                [
                    dmc.Title(list_item["title"], order=4, mb="sm"),
                    dmc.Progress(value=progress_value, color=progress_color),
                ],
                id={"type": "list_button", "index": list_item["index"]},
                style={"cursor": "pointer"},
            ),
            p="xs",
            mb="sm",
            withBorder=True,
            className="active" if current_index == list_item["index"] else ""
        )
        items.append(elem)

    return items

def get_progression(list_item):
    """ Computes the progression of a list """
    tasks = list_item["tasks_list"]
    if len(tasks) == 0:
        return 0
    return len([task for task in tasks if task["checked"]]) / len(tasks) * 100
</code></pre>



<p>We simply iterate over the lists in <code>list_data</code>. For each list, we compute the progression (the progress bar turns green at 100% instead of blue) and make the cards with title and progress bar.</p>



<p class="callout"><strong>Notice</strong> that we wrapped the content inside a <code>html.A</code> button. That way, we make the card clickable and we will be able to create a callback listening for clicks (<code>n_clicks</code>) on the card component.</p>



<p>The <code>current_index</code> parameter will store the <code>index</code> of the list we’re currently displaying. We use it to set the CSS class “active” to this list, making it focused compared to the others.</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* Filepath: assets/style.css */
/* ... (previous code) ... */

#list_navigation_layout &gt; div {
    opacity: 0.7; 
}

/* Opacity is a good way to emphasize some elements */
#list_navigation_layout &gt; div.active {
    border-color: black;
    opacity: 1.0;
} 
</code></pre>



<p>We also need a &#8220;New list&#8221; button:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_new_list_button():
    return dmc.Button(
        "New list",
        id="new_list_button",
        style={"width": "100%"},
        color="black",
        mt="md",
        mb="md"
    )
</code></pre>



<h3 id='3-updated-app-layout'  id="boomdevs_11" class="wp-block-heading" >3. Updated app layout</h3>



<p>Finally, we use DMC’s grid system to create a two-panel layout:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">app.layout = dmc.MantineProvider(
    [
        dmc.Container(
            dmc.Grid(
                [
                    dmc.GridCol(
                        [
                            get_new_list_button(),  # The new list button
                            dmc.Container(          # This container will be updated dynamically
                                id="list_navigation_layout",
                                px=0,
                            )
                        ],
                        span=4,
                    ),
                    dmc.GridCol(
                        get_list_layout(),
                        span="auto",
                        ml="xl"
                    ),
                ],
                gutter="md"
            ),
            size=600,
        ),
        
        # The stored data in localStorage
        dcc.Store("list_data_memory", data=sample_list_data, storage_type="local"),
        dcc.Store("current_index_memory", data=sample_list_data[0]["index"], storage_type="local"),
    ]
)
</code></pre>



<p>The left panel serves as a navigation sidebar, displaying all available lists with their progress bars. The right panel shows the tasks of the currently selected list, maintaining our familiar task interface but adding an editable title and a list deletion option.</p>



<p>Here’s the interactive version of the new layout:</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/todo_app/app4.py/" width="100%" height="500" frameBorder="0"></iframe>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/Spriteware/todo-app-python-dash/blob/master/app4.py">See the full code on Github</a> or <a href="https://scripts.dash-resources.com/todo_app/app4.py/">open the app</a>.</p>



<p>In the next section, we&#8217;ll add the necessary callbacks to make this layout interactive.</p>



<h3 id='4-list-management-callbacks'  id="boomdevs_12" class="wp-block-heading" >4. List management callbacks</h3>



<p>With multiple lists to manage, we need callbacks to handle list-level operations.</p>



<p>The list switching callback responds to clicks in the navigation sidebar, updating <code>current_index_memory</code> to display the selected list:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("current_index_memory", "data", allow_duplicate=True),
    Input({"type": "list_button", "index": ALL}, "n_clicks"),
    prevent_initial_call=True,
)
def switch_list(n_clicks):
    """ Changes the current displayed list"""
    if not any(n_clicks):
        raise PreventUpdate

    list_index = ctx.triggered_id["index"]
    return list_index
</code></pre>



<p>When users create a new list, the <code>add_list</code> callback adds it to our collection and automatically makes it the current list by updating <code>current_index_memory</code>.</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    [
        Output("list_data_memory", "data", allow_duplicate=True),
        Output("current_index_memory", "data"),
    ],
    Input("new_list_button", "n_clicks"),
    State("list_data_memory", "data"),
    prevent_initial_call=True,
)
def add_list(n_clicks, list_data):
    """ Add a new list and display it"""
    if not n_clicks:
        raise PreventUpdate

    new_index = uuid.uuid4().hex
    new_list = {
        "index": new_index,
        "title": "New list",
        "tasks_list": [],
    }

    list_data.append(new_list)
    return list_data, new_index
</code></pre>



<p>You’ll notice how the code is similar to the <code>add_task</code> callback.</p>



<p>List deletion works in two steps: first showing a confirmation modal, then removing the list if confirmed. That way, the UX (User Experience) is better and avoids mistakingly deleting lists! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("del_list_modal", "opened"),
    [
        Input("del_list_button", "n_clicks"),
        Input("del_list_modal_confirm_button", "n_clicks"),
    ],
    prevent_initial_call=True,
)
def delete_modal_open_close(n_clicks, n_clicks2):
    """ Open or closes modal """
    if not n_clicks:
        raise PreventUpdate

    if ctx.triggered_id == "del_list_modal_confirm_button":
        return False

    return True

@app.callback(
    [
        Output("list_data_memory", "data", allow_duplicate=True),
        Output("current_index_memory", "data", allow_duplicate=True),
    ],
    [
        Input("del_list_modal_confirm_button", "n_clicks"),
        State("list_data_memory", "data"),
        State("current_index_memory", "data"),
    ],
    prevent_initial_call=True,
)
def delete_list(n_clicks, list_data, current_index):
    """ Updates the current list title """
    if not n_clicks:
        raise PreventUpdate

    # Remove the current list
    i = get_pos_from_index(list_data, current_index)
    del list_data[i]

    # Focus again on the first index if there are notes
    current_index = list_data[0]["index"] if len(list_data) &gt; 0 else None
    return list_data, current_index

</code></pre>



<p>We used <code>ctx.triggered_id</code> to differentiate whether we needed to open or close the modal. It’s a simple way to update the same component without relying on <code>allow_duplicate=True</code> and making two separate callbacks.</p>



<p class="callout"><strong>Note</strong>: you might wonder why the Outputs and Inputs are now wrapped inside lists: <code>[Output(…), Output(…)]</code>. In this case, this is just syntax sugar that helps visualizing what are the inputs / outputs easily. I often do this as the outputs or inputs grows.</p>



<p>We&#8217;ve added a helper function <code>get_pos_from_index</code> that simplifies finding lists in our data structure &#8211; it&#8217;s used throughout our callbacks to ensure we&#8217;re modifying the correct list:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_pos_from_index(dict_list, index):
    """ Gets position of dict with matching index in a list
			  Example:
	      * get_pos_from_index([{"index": "abc"}, {"index": "def"}], "def")  
	      * returns 1
    """
    for i, elem in enumerate(dict_list):
        if elem["index"] == index:
            return i
    return None
</code></pre>



<p>The helper work both for lists and for tasks.</p>



<h3 id='5-updated-task-callbacks'  id="boomdevs_13" class="wp-block-heading" >5. Updated task callbacks</h3>



<p>Finally, we need to update our task callbacks to work with the currently displayed list (using <code>current_index_memory</code>):</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("list_data_memory", "data", allow_duplicate=True),
    [
        Input("new_task_button", "n_clicks"),
        State("list_data_memory", "data"),
        State("current_index_memory", "data"),
    ],
    prevent_initial_call=True,
)
def add_task(n_clicks, list_data, current_index):
    """ Adds a task to the list """
    if not n_clicks:
        raise PreventUpdate

    # Create new task dictionary
    new_index = uuid.uuid4().hex
    new_task = {
        "index": new_index,
        "content": "",
        "checked": False,
    }

    # Add new task to the tasks list in memory
    i = get_pos_from_index(list_data, current_index)
    list_data[i]["tasks_list"].append(new_task)

    return list_data
</code></pre>



<p>Similar updates are needed for the remove and update task callbacks. The key change is using <code>get_pos_from_index()</code> to find the current list in our data and updating it.</p>



<h3 id='6-run-the-app-1'  id="boomdevs_14" class="wp-block-heading" >6. Run the app</h3>



<p>Here is the final result. Try switch from a list to another, add list, remove, modify tasks… And reload the page!</p>



<p>Here’s the interactive version of the new layout:</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/todo_app/app5.py/" width="100%" height="500" frameBorder="0"></iframe>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/Spriteware/todo-app-python-dash/blob/master/app5.py">See the full code on Github</a> or <a href="https://scripts.dash-resources.com/todo_app/app5.py/">open the app</a>.</p>



<p>You can also download the entire project below:</p>



                <div class="ml-embedded" data-form="LGDFB1"></div>
            



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='conclusion'  id="boomdevs_15" class="wp-block-heading" >Conclusion</h2>



<p>Throughout this tutorial, we&#8217;ve taken our basic task list and transformed it into a more powerful application. We could go even further: adding user login/register, share task lists, etc. Feel free to implement them on your side! </p>



<p>In this tutorial we’ve covered key concepts:</p>



<ul class="wp-block-list">
<li>Using browser storage with <code>dcc.Store</code></li>



<li>Managing complex state across multiple components</li>



<li>Building modular layouts with DMC Grid</li>



<li>Handling nested data structures in callbacks</li>
</ul>



<p>In the final part of this series, we&#8217;ll look at improving performance using Dash&#8217;s Patch system.</p>



<p>You can find the complete code for this tutorial on Github: <a href="https://github.com/Spriteware/todo-app-python-dash">https://github.com/Spriteware/todo-app-python-dash</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>I hope you enjoyed this tutorial. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>You can ask questions on the associated topic on Plotly’s community forum: <a href="https://community.plotly.com/t/tutorial-build-a-to-do-app-in-python-with-dash/89805">here</a>.<br><strong>Be sure to subscribe to the newsletter to see the next articles!</strong></p>



<p></p>
<p>L’article <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">Build a To-Do app in Python with Dash (part 2/3)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Build a To-Do app in Python with Dash (part 1/3)</title>
		<link>https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Wed, 08 Jan 2025 18:58:49 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[pattern-matching]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=409</guid>

					<description><![CDATA[<p>In this tutorial, we&#8217;ll build a To-Do application 100% in Python using Dash plotly and the community extension Dash Mantine Components. We&#8217;ll take an iterative [...]</p>
<p>L’article <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/">Build a To-Do app in Python with Dash (part 1/3)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we&#8217;ll build a To-Do application 100% in Python using Dash plotly and the community extension Dash Mantine Components. We&#8217;ll take an iterative approach, starting with the basics and gradually adding complexity as we understand why each piece is needed.</p>



<p>The tutorial is in three parts:</p>



<ol class="wp-block-list">
<li>Setup the layout, handle a minimal task list (part 1 &#8211; this article)</li>



<li><a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">Handle multiple lists and save tasks on page reload (part 2)</a></li>



<li><a href="https://dash-resources.com/writing-in-process/">Improve scalability and performance with Patch (part 3)</a></li>
</ol>



<p>At the end of this article, you’ll know how to build the following To-Do app with Dash python:</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/todo_app/app2.py/" width="100%" height="400" frameBorder="0"></iframe>



<p>Let’s go!</p>



<h2 id='introduction-to-dash-mantine-components'  id="boomdevs_1" class="wp-block-heading" >Introduction to Dash Mantine Components</h2>



<p>Before we dive in, let&#8217;s understand what <a href="https://www.dash-mantine-components.com/">Dash Mantine Components</a> (DMC) offers us. DMC is a component library that provides pre-built React components with a modern design system. It includes layout helpers (e.g. grid systems, centered container, &#8230;) and out-of-the-box components (e.g. Modal, Radio Button, Slider, etc.) </p>



<figure class="wp-block-image size-full is-style-default"><img loading="lazy" decoding="async" width="1796" height="684" src="https://dash-resources.com/wp-content/uploads/2025/01/image.png" alt="Illustration: a modal is a typical example of what DMC can provide out-of-the-box." class="wp-image-410" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image.png 1796w, https://dash-resources.com/wp-content/uploads/2025/01/image-300x114.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-1024x390.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-768x292.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-1536x585.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-1320x503.png 1320w" sizes="auto, (max-width: 1796px) 100vw, 1796px" /><figcaption class="wp-element-caption">Illustration: a modal is a typical example of what DMC can provide out-of-the-box.</figcaption></figure>



<p>Some key components we&#8217;ll use:</p>



<ul class="wp-block-list">
<li><code>dmc.Container</code>: A centered container with max-width</li>



<li><code>dmc.Paper</code>: A white background container with optional shadow</li>



<li><code>dmc.Grid</code> and <code>dmc.GridCol</code>: Flexbox-based grid system</li>



<li><code>dmc.Button</code>, <code>dmc.Checkbox</code>, <code>dmc.ActionIcon</code>: Interactive components</li>
</ul>



<p>DMC uses several spacing and positioning attributes:</p>



<ul class="wp-block-list">
<li><code>mt</code>: margin-top (e.g., <code>mt="md"</code> for medium margin-top)</li>



<li><code>mb</code>: margin-bottom</li>



<li><code>px</code>: padding on x-axis (left and right)</li>



<li><code>p</code>: padding on all sides</li>



<li>Size values: &#8220;xs&#8221;, &#8220;sm&#8221;, &#8220;md&#8221;, &#8220;lg&#8221;, &#8220;xl&#8221; or numeric pixels</li>
</ul>



<p>This will be useful for the rest of the article. If you encounter something you don’t understand, just go back here or search the component in <a href="https://www.dash-mantine-components.com/">DMC documentation</a>.</p>



<h2 id='step-1-setting-up-the-layout'  id="boomdevs_2" class="wp-block-heading" >Step 1: Setting up the layout</h2>



<h3 id='1-data-structure'  id="boomdevs_3" class="wp-block-heading" >1. Data structure</h3>



<p>Let&#8217;s start by defining how we&#8217;ll store our task data. This will actually help us shape the layout.</p>



<p>We want to create and save tasks. Each task should have a text content, and a checkbox status. That means having something like:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">sample_list_data = {
    "title": "My Tasks",
    "tasks_list": [
        {
            "content": "Task A",
            "checked": True,
        },
        {
            "content": "Task B",
            "checked": False,
        },
        {
            "content": "Task C",
            "checked": False,
        },
    ],
}

</code></pre>



<p>From this structure, we can build the layout function to display tasks.</p>



<h3 id='2-creating-reusable-layout-functions'  id="boomdevs_4" class="wp-block-heading" >2. Creating reusable layout functions</h3>



<p>A key aspect of our design is creating separate functions for each part of the layout. This isn&#8217;t just for code organization &#8211; it&#8217;s crucial for the dynamic nature of our app. Later, when we add or remove tasks, we&#8217;ll need to recreate portions of the layout dynamically. By having these functions, we can easily generate new task elements or update existing ones.</p>



<p>Let&#8217;s start with a function to render a single task using DMC&#8217;s Grid system:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_task(task_dict):
    """ Returns a single task layout """
    text = task_dict["content"]
    checked = task_dict["checked"]

    content = dmc.Grid(
        [
            # Checkbox column
            dmc.GridCol(
                dmc.Checkbox(
                    checked=checked,
                    mt=2  # Align checkbox vertically
                ),
                span="content"  # Take only needed space
            ),
            # Task text column - Using Input for editability
            dmc.GridCol(
                dmc.Text(  # Wrap in Text component for consistent styling
                    dcc.Input(
                        text,
                        className="shadow-input",  # Custom styling for input
                        debounce=True              # For callbacks
                    )
                ),
                span="auto"  # Take remaining space
            ),
            # Delete button column
            dmc.GridCol(
                dmc.ActionIcon(
                    DashIconify(icon="tabler:x", width=20),
                    variant="transparent",
                    color="gray",
                    className="task-del-button"
                ),
                span="content"
            ),
        ],
        className="task-container"
    )

    return content

</code></pre>



<p>We used <code>dmc.Grid</code> to easily get a 3 column structure. The first column has the checkbox, the second the text content, and the third has the delete task button.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="836" height="624" src="https://dash-resources.com/wp-content/uploads/2025/01/image-1.png" alt="Illustration for the 3 column grid structure." class="wp-image-411" style="width:389px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-1.png 836w, https://dash-resources.com/wp-content/uploads/2025/01/image-1-300x224.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-1-768x573.png 768w" sizes="auto, (max-width: 836px) 100vw, 836px" /><figcaption class="wp-element-caption">Illustration for the 3 column grid structure.</figcaption></figure>
</div>


<p>You might wonder why we use a <code>dcc.Input</code> wrapped in a <code>dmc.Text</code> component. The idea is that we want to modify the text just by clicking on it. The most similar example is the <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/contenteditable">“contenteditable” elements</a> in HTML : but they aren’t available in Dash.</p>



<p>Instead, we will style the app with some CSS to make the input look like normal text when it is not focused (i.e. being modified).</p>



<p class="callout"><strong>Note</strong>: the <code>debounce</code> attribute is used to trigger the input “update” event when the user finishes typing, not every time a key is pressed. In this case, it offers a better user experience.</p>



<h3 id='3-creating-the-tasks-list-and-main-container'  id="boomdevs_5" class="wp-block-heading" >3. Creating the tasks list and main container</h3>



<p>Now let&#8217;s create a function to render all tasks. It’s simply the for loop over the <code>get_task</code> function:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_tasks_layout(tasks_list):
    """ Returns the list of tasks """
    tasks = []
    for task_dict in tasks_list:
        task_layout = get_task(task_dict)
        tasks.append(task_layout)
    return tasks

</code></pre>



<p>We&#8217;ll wrap everything in a main container with a title and &#8220;Add a new ask&#8221; button:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_list_layout(list_data):
    """ Returns the list container with title and tasks """
    tasks_layout = get_tasks_layout(list_data["tasks_list"])

    content = dmc.Paper(
        [
            dmc.Title(list_data["title"], order=2),

            # Tasks container
            dmc.Container(
                tasks_layout,
                id="main_task_container",
                px=0,  # No horizontal padding
                mt="md",  # Medium margin top
                mb="md",  # Medium margin bottom
            ),

            # Add task button
            dmc.Button(
                "Add a new task",
                id="new_task_button",
                style={"width": "100%"},
                variant="outline",
                color="gray",
            )
        ],
        shadow="sm",  # Light shadow
        p="md",       # Medium padding
        mt="md",      # Medium margin top
        radius="sm",  # Slightly rounded corners
    )

    return content

</code></pre>



<h3 id='4-styling-with-css'  id="boomdevs_6" class="wp-block-heading" >4. Styling with CSS</h3>



<p>DMC already brings a set of default stylesheet, which make us gain a lot of time. But as said previously, we want our we want our task inputs to look clean and minimal, with appropriate visual feedback for user interactions:</p>



<p>So we create a <code>style.css</code> file in the <code>assets/</code> folder. This will be loaded automatically by Dash:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* 
 * Filepath: ./assets/style.css 
 */

.shadow-input {
    display: inline-block;
    width: 100%;
    margin: 0;
    border: 1px solid black;
    border-color: transparent;
    border-radius: 5px;
}
.shadow-input:hover {
    border-color: gray;
}

.shadow-input:active, .shadow-input:focus {
    border-color: black;
}

.task-container:has(.mantine-Checkbox-root[data-checked]) .mantine-Text-root input {
    text-decoration: line-through;
    color: gray;
}

.task-del-button:hover {
    color: red;
}
</code></pre>



<p>This CSS does several things:</p>



<ol class="wp-block-list">
<li>Makes inputs blend seamlessly into the layout with transparent borders</li>



<li>Shows subtle borders on hover and focus for better UX</li>



<li>Applies strikethrough styling to checked tasks</li>



<li>Adds a red hover effect to delete buttons</li>
</ol>



<p>A key design decision here is handling the strikethrough effect with CSS rather than callbacks. By using the <code>:has()</code> selector to detect checked checkboxes, we can apply the strikethrough style directly in CSS. This is more efficient than using a callback, as it eliminates unnecessary round-trips to the server and provides instant visual feedback.</p>



<h3 id='5-running-the-app'  id="boomdevs_7" class="wp-block-heading" >5. Running the app</h3>



<p>Now let&#8217;s put everything together and start our application:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, dcc
from dash_iconify import DashIconify
_dash_renderer._set_react_version("18.2.0")  # for mantine

# sample_list_data = ...

## Layout functions:
# get_task()
# get_tasks_layout()
# get_list_layout

app = Dash(__name__)

app.layout = dmc.MantineProvider(
    dmc.Container(
        get_list_layout(sample_list_data),
        size=400,  # Container width
    )
)

# Start the server
if __name__ == '__main__':
    app.run_server(debug=True)

</code></pre>



<p>When you run this code, you&#8217;ll have a basic task list application without interactivity yet:</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/todo_app/app1.py/" width="100%" height="400" frameBorder="0"></iframe>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/Spriteware/todo-app-python-dash/blob/master/app1.py">See the full code on Github</a> or <a href="https://scripts.dash-resources.com/todo_app/app1.py/">open the app</a>.</p>



<p>Now that we have our layout defined, we can make it interactive with callbacks.</p>



<h2 id='step-2-adding-interactivity-with-callbacks'  id="boomdevs_8" class="wp-block-heading" >Step 2: Adding interactivity with callbacks</h2>



<p>Now comes the interesting part. We need to make our tasks interactive &#8211; we need to handle creating, reading, updating, and deleting tasks (<a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD operations</a>). However, we face an interesting challenge: how do we handle callbacks for elements that don&#8217;t exist when the app starts?</p>



<p>In regular Dash callbacks, you must define both inputs and outputs statically &#8211; they need to exist when the app initializes. This works fine for static elements like a single button or dropdown. But in our task app, we&#8217;re dynamically adding and removing tasks! These elements don&#8217;t exist when the app starts running.</p>



<p>This is where pattern-matching callbacks come in. Instead of targeting specific IDs like &#8220;button-1&#8221; or &#8220;task-input-2&#8221;, pattern-matching callbacks let us define patterns that match multiple components, even ones created after the app starts running. They work by using a dictionary-based ID system and special selectors like <code>ALL</code>.</p>



<p>For example, compare these two approaches:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Regular callback - Only works for a specific, existing button
@app.callback(
    Output("static-div", "children"),
    Input("submit-button", "n_clicks")
)

# Pattern-matching callback - Works for ANY button matching the pattern
@app.callback(
    Output({"type": "result", "id": ALL}, "children"),
    Input({"type": "submit", "id": ALL}, "n_clicks")
)
</code></pre>



<p>The pattern-matching version uses a dictionary for the ID with two keys:</p>



<ul class="wp-block-list">
<li>&#8220;type&#8221;: Groups similar components (like all submit buttons)</li>



<li>&#8220;id&#8221;: Uniquely identifies each instance</li>
</ul>



<p>Now we can write a single callback that handles all tasks of the same type, even ones created dynamically after the app starts! When we add a new task with ID <code>{"type": "task", "id": "123"}</code>, the callback will automatically work for it.</p>



<p>This is perfect for our task app where we need to add and remove tasks dynamically. Let’s continue.</p>



<p class="callout"><strong>Learn more</strong> on Pattern-Matching callbacks from the Dash plotly documentation: <a href="https://dash.plotly.com/pattern-matching-callbacks">https://dash.plotly.com/pattern-matching-callbacks</a></p>



<h3 id='1-adding-unique-identifiers'  id="boomdevs_9" class="wp-block-heading" >1. Adding unique identifiers</h3>



<p>As Pattern-Matching callbacks need a unique id, we need to modify our data structure to include unique identifiers for each task. Let&#8217;s add an <code>index</code> field using UUID :</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import uuid

sample_list_data = {
    "title": "My Tasks",
    "tasks_list": [
        {
            "index": uuid.uuid4().hex,  # Add unique identifier
            "content": "Task A",
            "checked": True,
        },
        # ... other tasks
    ],
}

</code></pre>



<p>Example UUID: <code>f4da57942cec46b7ba448c88fad11996</code>. We could as well have used integers. It doesn’t matter as long as the ids are unique.</p>



<p>Now we need to update our <code>get_task</code> function to use these identifiers:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_task(task_dict):
    """ Returns a single task layout """
    text = task_dict["content"]
    checked = task_dict["checked"]
    index = task_dict["index"]  # Get the index

    content = dmc.Grid(
        [
            dmc.GridCol(
                dmc.Checkbox(
                    id={"type": "task_checked", "index": index},  # Add ID
                    checked=checked,
                    mt=2
                ),
                span="content"
            ),
            dmc.GridCol(
                dmc.Text(
                    dcc.Input(
                        text,
                        id={"type": "task_content", "index": index},  # Add ID
                        className="shadow-input",
                        debounce=True,
                    )
                ),
                span="auto"
            ),
            dmc.GridCol(
                dmc.ActionIcon(
                    DashIconify(icon="tabler:x", width=20),
                    id={"type": "task_del", "index": index},  # Add ID
                    variant="transparent",
                    color="gray",
                    className="task-del-button"
                ),
                span="content"
            ),
        ],
        className="task-container"
    )

    return content

</code></pre>



<p>We now have a unique index on the three interactive components : the checkbox, the input and the delete button. Let’s write the callbacks.</p>



<h3 id='2-adding-new-tasks'  id="boomdevs_10" class="wp-block-heading" >2. Adding new tasks</h3>



<p>Now we can implement the &#8220;Add Task&#8221; functionality using callbacks:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("main_task_container", "children", allow_duplicate=True),
    Input("new_task_button", "n_clicks"),
    State("main_task_container", "children"),
    prevent_initial_call=True,
)
def add_task(n_clicks, current_tasks):
    """ Adds a task to the list """
    if not n_clicks:
        raise PreventUpdate

    # Create new task with unique ID
    new_index = uuid.uuid4().hex
    task_dict = {
        "index": new_index,
        "content": "",
        "checked": False,
    }
    task_layout = get_task(task_dict)

    # Add new task to current tasks
    updated_tasks = current_tasks + [task_layout]
    return updated_tasks

</code></pre>



<p>This callback is triggered with the new_task_button is clicked. It basically takes the existing list of tasks that are in <code>main_task_container</code> and add a new, empty, task inside. Then it returns the new list.</p>



<p>We added <code>prevent_initial_call=True</code> to avoid running this callback when the app is loaded (the default behavior), as we know that this callback should only be triggered on a button action. It reduces unnecessary work and HTTP requests.</p>



<p>We also check the <code>n_clicks</code> is a valid positive value, in case that the callback gets triggered anyway. The <code>raise PreventUpdate</code> stops the callback execution.</p>



<p class="callout"><strong>Notice</strong> the <code>allow_duplicate=True</code> on the <code>Output</code>. It is mandatory as we will have many operations that will update the <code>main_task_container</code> component.</p>



<h3 id='3-removing-tasks'  id="boomdevs_11" class="wp-block-heading" >3. Removing Tasks</h3>



<p>And finally, we implement the “Task deletion” callback following the same scheme:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("main_task_container", "children", allow_duplicate=True),
    Input({"type": "task_del", "index": ALL}, "n_clicks"),
    State("main_task_container", "children"),
    prevent_initial_call=True,
)
def remove_task(n_clicks, current_tasks):
    """ Remove a task from the list """
    if not any(n_clicks):
        raise PreventUpdate

    print("Entering remove_task callback")
    task_index = ctx.triggered_id["index"]

    # Get the list of existing ids.
    all_ids = [elem["id"] for elem in ctx.inputs_list[0]]

    # Find the position of element in list and remove it
    for i, task_id in enumerate(all_ids):
        if task_id["index"] == task_index:
            del current_tasks[i]
            break 

    return current_tasks
</code></pre>



<p>Here&#8217;s how it works:</p>



<ol class="wp-block-list">
<li>When a delete button is clicked, Dash triggers this callback. We use pattern matching with <code>{"type": "task_del", "index": ALL}</code> to catch clicks from any delete button in our tasks. Each button has a unique index we assigned earlier.</li>



<li>The <code>ctx.triggered_id</code> tells us which specific delete button was clicked &#8211; specifically its index. This works because when the callback fires, Dash knows exactly which component triggered it.</li>



<li>To find and remove the correct task, we need to:
<ul class="wp-block-list">
<li>Get all delete button IDs using <code>ctx.inputs_list[0]</code> which contains the IDs of all components matching our pattern</li>



<li>Map this to just get the ID dictionaries (each with a &#8220;type&#8221; and &#8220;index&#8221;)</li>



<li>Find the position (index) in our task list that matches the triggered button&#8217;s index</li>



<li>Delete that task from <code>current_tasks</code> using <code>del</code></li>
</ul>
</li>
</ol>



<p>As seen previously, the <code>prevent_initial_call=True</code> and <code>if not any(n_clicks)</code> check ensure we don&#8217;t accidentally delete tasks when the app first loads or if the callback is triggered without a click.</p>



<h3 id='4-run-the-app'  id="boomdevs_12" class="wp-block-heading" >4. Run the app</h3>



<p>Let’s run again the code. We preferably place our callbacks before the <code>app.run_server</code> statement:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import uuid
import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, dcc, ctx, Input, Output, State, ALL
from dash.exceptions import PreventUpdate
from dash_iconify import DashIconify
_dash_renderer._set_react_version("18.2.0")  # for mantine

# sample_list_data = ...

## Layout functions:
# get_task()
# get_tasks_layout()
# get_list_layout

app = Dash(__name__)

# app.layout = ..

## Callbacks
# add_task()
# remove_task()

# Start the server
if __name__ == '__main__':
    app.run_server(debug=True)
</code></pre>



<p>Try to add a task, modify and delete a task in the app below:</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/todo_app/app2.py/" width="100%" height="400" frameBorder="0"></iframe>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/Spriteware/todo-app-python-dash/blob/master/app2.py">See the full code on Github</a> or <a href="https://scripts.dash-resources.com/todo_app/app2.py/">open the app</a>.</p>



<p>You can also download the full project below:</p>



                <div class="ml-embedded" data-form="LGDFB1"></div>
            



<p>You now have a fully working —yet basic— todo app. Congratulations!</p>



<h2 id='conclusion'  id="boomdevs_13" class="wp-block-heading" >Conclusion</h2>



<p>Let&#8217;s recap what we covered in this article:</p>



<ul class="wp-block-list">
<li>An introduction to DMC (Dash Mantine Components) and its components</li>



<li>How to use pattern matching callbacks for dynamic interactions</li>



<li>How to use CSS styling in a Dash context</li>
</ul>



<p>In the next part, we will see how to adapt this code to handle multiple lists and keep the tasks saved after page reloads (persistence): <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">How to build a To-Do app, part 2</a>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>I hope you enjoyed this first part of the tutorial! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>If you have any question, please join us on the dedicated topic on Plotly’s forum: <a href="https://community.plotly.com/t/tutorial-build-a-to-do-app-in-python-with-dash/89805">here</a>. <strong>Get notified of a new article by subscribing to the newsletter. </strong></p>
<p>L’article <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/">Build a To-Do app in Python with Dash (part 1/3)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
