<?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 Intermediate level | dash-resources.com</title>
	<atom:link href="https://dash-resources.com/category/intermediate-level/feed/" rel="self" type="application/rss+xml" />
	<link>https://dash-resources.com/category/intermediate-level/</link>
	<description>Learn to build interactive web applications with Python and Dash plotly</description>
	<lastBuildDate>Tue, 17 Feb 2026 23:03:55 +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 Intermediate level | dash-resources.com</title>
	<link>https://dash-resources.com/category/intermediate-level/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<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 fetchpriority="high" 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="(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 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="(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 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="(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>Improving Dash DataTables: Simple CSS Tweaks</title>
		<link>https://dash-resources.com/improving-dash-datatables-simple-css-tweaks/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sun, 19 Jan 2025 12:06:50 +0000</pubDate>
				<category><![CDATA[Intermediate level]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[datatable]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=527</guid>

					<description><![CDATA[<p>The Dash DataTable is a powerful component to display tabular data. However, I’ve always felt it didn’t seamlessly integrate with the applications I was developing. [...]</p>
<p>L’article <a href="https://dash-resources.com/improving-dash-datatables-simple-css-tweaks/">Improving Dash DataTables: Simple CSS Tweaks</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>The <a href="https://dash.plotly.com/datatable">Dash DataTable</a> is a powerful component to display tabular data. However, I’ve always felt it didn’t seamlessly integrate with the applications I was developing.</p>



<p>In this article, I’ll show you how you can improve the user interface and user experience (UI/UX) of a DataTable by tweaking some parts using CSS (<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;" /> <strong><a href="#interactive-app">interactive app below</a></strong> <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>


        
            
            <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="#1-add-bootstrap-styles">1 &#8211; Add Bootstrap styles</a>
      </li>
      <li>
        <a href="#2-improve-the-header">2 &#8211; Improve the header</a>
      </li>
      <li>
        <a href="#3-striped-rows">3 &#8211; Striped rows</a>
      </li>
      <li>
        <a href="#4-hover-effect">4 &#8211; Hover effect</a>
      </li>
      <li>
        <a href="#5-row-selection">5 &#8211; Row selection</a>
      </li>
      <li>
        <a href="#how-to-modify-styles-yourself">How to modify styles yourself</a>
      </li>
      <li>
        <a href="#how-to-reuse-this-code">How to reuse this code</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 dive in!</p>



<h2 id='1-add-bootstrap-styles'  id="boomdevs_1" class="wp-block-heading" >1 &#8211; Add Bootstrap styles</h2>



<p>First things first: the DataTable’s basic styles don’t integrate well with an app. The main reason is that many styles, such as the font-family, are hardcoded.</p>



<p>If you work with Dash Bootstrap Components, this becomes obvious. Let’s take a simple app as an example:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import pandas as pd
import numpy as np
import dash_bootstrap_components as dbc
from dash import Dash, html, Input, Output, dash_table

# Create sample data
np.random.seed(42)
size = 20
df = pd.DataFrame({
    "id": range(size),
    "x": np.random.normal(0, 1, size),
    "y": np.random.normal(0, 1, size),
    "category": np.random.choice(["A", "B", "C"], size)
})

# Initialize the Dash app
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

# Define the layout
app.layout = html.Div(
    [
        # Data table
        html.Div(
            dash_table.DataTable(
                id="table",
                columns=[
                    {"name": i, "id": i, "deletable": True, "selectable": True} for i in df.columns
                ],
                data=df.to_dict("records"),

                # Enable multi-row selection and paging
                filter_action="native",
                sort_action="native",
                sort_mode="multi",
                column_selectable="multi",
                row_selectable="multi",  
                page_action="native",
                page_size=10,
            ),
            id="table_container",
        )
    ],
    className="container"
)

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)
</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="654" src="https://dash-resources.com/wp-content/uploads/2025/01/image-8.png" alt="Illustration: the default DataTable is not looking like a dash boostrap table at all." class="wp-image-528" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-8.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-8-300x96.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-8-1024x327.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-8-768x245.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-8-1536x491.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-8-1320x422.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: the default DataTable is looking a bit &#8220;raw&#8221; compared to modern themes like Bootstrap.</figcaption></figure>



<p>The solution is to overwrite the DataTable’s styles with Bootstrap styles. Fortunately, <strong>Ann Marie Ward</strong>, a major Dash contributor, has already created a solution for this <a href="https://hellodash.pythonanywhere.com/adding-themes/DataTable">here</a> ( thank you Ann if you see this <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f64f.png" alt="🙏" class="wp-smiley" style="height: 1em; max-height: 1em;" />).</p>



<p>Her CSS stylesheet includes styles to apply Bootstrap themes to Dash components, such as tables and buttons, for a consistent appearance. We’ll download it <a href="https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.css">(link)</a>, and save into into the assets folder at <code>assets/dbc.css</code>.</p>



<p>Then, we just need to add the <code>dbc</code> CSS class to a container <code>div</code> wrapping our DataTable:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Data table
html.Div(
    dash_table.DataTable(
        # ...
    ),
    id="table_container",
    className="dbc"  # we added the dbc class
)</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="633" src="https://dash-resources.com/wp-content/uploads/2025/01/image-9.png" alt="Illustration: the DataTable now has a Bootstrap theme, which integrates better in apps. But the pagination style is incorrect." class="wp-image-529" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-9.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-9-300x93.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-9-1024x317.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-9-768x237.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-9-1536x475.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-9-1320x408.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: the DataTable now has a Bootstrap theme, which integrates better in apps. But the pagination style is incorrect.</figcaption></figure>



<p>As you can see, the table now inherits the Bootstrap styles. However, the pagination remains untouched.</p>



<p>Unfortunately, there is no option to dynamically set the className of the buttons to Bootstrap’s <code>btn</code>, <code>btn-outline-primary</code>, and <code>btn-sm</code>. But we can copy their styles (e.g., from the Bootstrap documentation) and apply them to the four pagination buttons (<code>.first-page</code>, <code>.next-page</code>, <code>.previous-page</code>, <code>.last-page</code>):</p>



<pre class="wp-block-code"><code lang="css" class="language-css">
/* reset font styles on pagination buttons */
.dbc .dash-table-container .previous-next-container .page-number,
.dbc .dash-table-container .previous-next-container .page-number .current-page-container input.current-page {
    font-family: var(--bs-body-font-family);
    font-size: var(--bs-body-font-size);
    font-weight: var(--bs-body-font-weight);
    line-height: var(--bs-body-line-height);
    min-width: auto !important;
}

/* apply the same styles as .btn, .btn-sm, .btn-outline-primary */
.dbc .dash-table-container .previous-next-container {
    button {
        &amp;.first-page,
        &amp;.last-page,
        &amp;.previous-page,
        &amp;.next-page {
            display: inline-block;
            font-weight: 400;
            text-align: center;
            vertical-align: middle;
            user-select: none;
            padding: 0.25rem 0.5rem;
            margin: 0 0.25rem;
            font-size: 0.875rem;
            line-height: 1.5;
            border-radius: 0.2rem;
            transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
            color: var(--bs-primary);
            border: 1px solid var(--bs-primary);
            background-color: transparent;

            &amp;:hover {
                color: var(--bs-white) !important; /* override dbc.css */
                background-color: var(--bs-primary);
                border-color: var(--bs-primary);
            }

            &amp;:focus {
                box-shadow: 0 0 0 0.25rem var(--bs-primary-rgb, rgba(13, 110, 253, 0.25));
            }

            &amp;:active,
            &amp;.active {
                color: var(--bs-white);
                background-color: var(--bs-primary);
                border-color: var(--bs-primary);
            }

            &amp;:disabled,
            &amp;.disabled {
                color: var(--bs-primary);
                background-color: transparent;
                border-color: var(--bs-primary);
                opacity: 0.65;
                pointer-events: none;
            }
        }
    }
}</code></pre>



<p>CSS rules are applied by order of importance (priority) to an element. The more you add precision to the selection, the more weight the rule has:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* a rule targetting a button */
.dash-table-container button { ... }
/* equivalent, but more precise (more weight): */
body .dash-table-container .previous-next-container button {...} </code></pre>



<p>That’s why sometimes we see a long selector definition, and sometimes not —because in some cases, it’s too complex to increase the weight of a rule. Then <code>!important</code> keyword enable us to override some CSS properties without having to make a stronger rules.</p>



<p class="callout"><strong>Good to know:</strong> It’s better to avoid <code>!important</code> whenever possible because each use makes future overrides more complex. Imagine if the creators of Bootstrap had used <code>!important</code> everywhere—overriding their styles would have been a nightmare!</p>



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



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="630" src="https://dash-resources.com/wp-content/uploads/2025/01/image-10.png" alt="Illustration: we now have proper Bootstrap buttons." class="wp-image-530" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-10.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-10-300x92.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-10-1024x315.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-10-768x236.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-10-1536x473.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-10-1320x406.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: we now have proper Bootstrap buttons.</figcaption></figure>



<p>We now have a Bootstrap-styled DataTable. Much better!</p>



<h2 id='2-improve-the-header'  id="boomdevs_2" class="wp-block-heading" >2 &#8211; Improve the header</h2>



<p>There are a few things I don’t like about the default DataTable header, but thankfully, everything is fixable with CSS.</p>



<p>First, the header row should be highlighted:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* make the heading bold and with a gray background */
.header-style .dash-table-container th {
    background-color: var(--bs-gray-200) !important;
}

.header-style .dash-table-container th .column-header-name {
    font-weight: bold;
}

.header-style .dash-table-container th input::placeholder {
    /* overrides back a style set in dbc.css */
    background-color: transparent !important;
}</code></pre>



<p>If you are not familiar with CSS variables, take a look at <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">this documentation</a>. They allow using the Bootstrap without hardcoding them. It is useful for themes like dark/light themes that will only require a change of variable value.</p>



<p>Second, the icons in the header are not uniformly sized, vertically aligned, or evenly spaced. Let’s fix that:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* better homogeneous styling of buttons */
.header-style .column-actions {
    /* let us breath! */
    gap: 0.4em; 
    margin-right: 0.5em;
}

.header-style .column-actions &gt; span {
    /* align components vertically */
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.header-style .dash-table-container th input[type="radio"],
.header-style .dash-table-container th input[type="checkbox"] {
		/* fix inconsistent color and size */
    width: 1rem;
    height: 1rem;
    opacity: 0.6;
}

.header-style .dash-table-container th input[type="radio"]:checked,
.header-style .dash-table-container th input[type="checkbox"]:checked {
		/* when checked, reset opacity */
    opacity: 1;
}</code></pre>



<p>Finally, clicking on the case-sensitive filter (<code>aA</code>) causes the column width to change. Adding a transparent border of the same size resolves this issue:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">.header-style .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner th.dash-filter input.dash-filter--case--insensitive {
    /* avoid column width size change when clicking */
    border-style: solid;
    border-width: 2px;
    border-color: transparent;
}</code></pre>



<p>Remember the CSS weights for rules? The definition above is long on purpose: thanks to this, I don’t need to add <code>!important</code>.</p>



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



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="638" src="https://dash-resources.com/wp-content/uploads/2025/01/image-11.png" alt="Illustration: a DataTable with a better looking header." class="wp-image-531" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-11.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-11-300x93.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-11-1024x319.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-11-768x239.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-11-1536x479.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-11-1320x411.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: a DataTable with a better looking header.</figcaption></figure>



<p>At this point, there are two types of people: those who notice the differences and those who don’t. If you don’t, I don’t blame you—you live in a much simpler world than I do!</p>



<p>If you do notice the differences, you may experience the satisfaction of seeing these buttons properly arranged. <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='3-striped-rows'  id="boomdevs_3" class="wp-block-heading" >3 &#8211; Striped rows</h2>



<p>Displaying striped rows can make the table easier on the eyes. It’s a tiny detail, but combined with other enhancements (like mouseover color), it improves the overall user experience of your app.</p>



<p>Striped rows for the DataTable <a href="https://dash.plotly.com/datatable/style#striped-rows">can be achieved in Python</a>, but a simple CSS snippet will also do the job:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* Alternating row background */
.striped-style .dash-table-container td:not(.cell--selected) {
    background-color: transparent !important;
}

.striped-style .dash-table-container tr:nth-child(odd) {
    background-color: var(--bs-gray-100) !important;
}

.striped-style .dash-table-container tr:nth-child(even) {
    background-color: var(--bs-body-bg) !important;
}</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="638" src="https://dash-resources.com/wp-content/uploads/2025/01/image-12.png" alt="Illustration: striped lines help visualizing many rows." class="wp-image-532" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-12.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-12-300x93.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-12-1024x319.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-12-768x239.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-12-1536x479.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-12-1320x411.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: striped lines help visualizing many rows.</figcaption></figure>



<p>The CSS property <code>:nth-child</code> is the key here, as it allow selecting either odd or even rows <code>tr</code>. The cells <code>td</code> are made transparent as the background is changed at the row level.</p>



<h2 id='4-hover-effect'  id="boomdevs_4" class="wp-block-heading" >4 &#8211; Hover effect</h2>



<p>Adding a hover effect is a simple trick to enhance interactivity, even for static tables. It implicitly signals to the user that the table is interactive.</p>



<p>Here’s the CSS to add a hover effect:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* Hover row background */
.hover-style .dash-table-container tr:hover td:not(.cell--selected)  {
    background-color: var(--bs-gray-200) !important;
}</code></pre>



<p>The background is applied to all cells expect selected cells (i.e., active cells), using <code>td:not(.cell—selected)</code>.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1186" height="366" src="https://dash-resources.com/wp-content/uploads/2025/01/datatable_hover.gif" alt="Illustration: row hover is a simple trick to make a table look like “interactive”." class="wp-image-533"/><figcaption class="wp-element-caption">Illustration: row hover is a simple trick to make a table look like “interactive”.</figcaption></figure>



<p>The hover color is slightly darker (<code>—bs-gray-200</code> instead of <code>—bs-gray-100</code>) to remain visible even on striped rows.</p>



<h2 id='5-row-selection'  id="boomdevs_5" class="wp-block-heading" >5 &#8211; Row selection</h2>



<p>By default, clicking on a cell highlights it. However, in most cases, it’s more intuitive to highlight the entire row. It is also helpful to emphasize rows selected via the left checkboxes. Let’s address this.</p>



<p>This code is slightly more complex:</p>



<ul class="wp-block-list">
<li>We style rows that have the <code>.cell--selected</code> class. This includes rows selected via checkboxes or mouse clicks.</li>



<li>To avoid overlapping borders, we use the adjacent sibling selector (<code>+</code>) to remove duplicate borders. For example, instead of <code>|X||X|</code>, we get <code>|X|X|</code>.</li>
</ul>



<pre class="wp-block-code"><code lang="css" class="language-css">/* apply style to selected cell */
.selection-style .dash-table-container tr:has(td.cell--selected) td.cell--selected {
    background-color: rgba(var(--bs-primary-rgb), 0.2) !important;
    border: 1px solid var(--bs-primary) !important;
    outline: none !important;
}

/* apply styles to the other cells of selected rows */
.selection-style .dash-table-container tr:has(td.cell--selected) td {
    background-color: rgba(var(--bs-primary-rgb), 0.2) !important;
    border: 1px solid #d0d0d0 !important;
}

/* remove border between selected cells (vertically) */
.selection-style .dash-table-container tr:has(td.cell--selected) + tr:has(td.cell--selected) td.cell--selected {
    background-color: rgba(var(--bs-primary-rgb), 0.2) !important;
    border-top: none !important;
}

/* remove border between selected cells (horizontally) */
.selection-style .dash-table-container tr:has(td.cell--selected) td.cell--selected + td.cell--selected {
    border-left: none !important;
}

/* apply style to checked rows */
.selection-style .dash-table-container tr:has(input:checked) td {
    background-color: rgba(var(--bs-primary-rgb), 0.1) !important;
    border: 1px solid #d0d0d0 !important;
}
</code></pre>



<p>Most styles here use <code>!important</code> to remain compatible with the <code>dbc.css</code> styles. If we don’t keep the <code>dbc.css</code> styles, additional lines would be required for proper functionality:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* add dbc styles to selection-style */
body .selection-style .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner td {
    background-color: var(--bs-body-bg);
    color: var(--bs-body-color);
    border: var(--bs-border-width) solid var(--bs-border-color) !important;
}
.selection-style .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner table {
    border-collapse: inherit;
    border-spacing: unset;
}</code></pre>



<p>The selection works for rows selected via checkboxes, single mouse clicks, and multiple selections (holding <code>Shift</code>).</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="648" src="https://dash-resources.com/wp-content/uploads/2025/01/image-13.png" alt="Illustration: selected rows are not more visible." class="wp-image-534" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-13.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-13-300x95.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-13-1024x324.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-13-768x243.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-13-1536x486.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-13-1320x418.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: selected rows are not more visible.</figcaption></figure>



<p>Notice that row selection do not work well visually with striped rows, so I removed it from the above screenshot. Feel free to try it yourself with the <strong>interactive app</strong>!</p>



<h2 id='how-to-modify-styles-yourself'  id="boomdevs_6" class="wp-block-heading" >How to modify styles yourself</h2>



<p>You might wonder how I came up with these precise CSS rules while reading this article.</p>



<p>Overriding CSS styles involves trial and error. Essentially, you need to use developer tools to inspect the applied rules on the target element and figure out how to “override” those rules.</p>



<p>Here’s how you can do it :</p>



<ol class="wp-block-list">
<li><em>Right click</em> in the browser on the element &gt; <em>Inspect</em></li>



<li>Select the element in the browser in the list if necessary</li>



<li>Look at the CSS styles applied to this element.</li>
</ol>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="1098" src="https://dash-resources.com/wp-content/uploads/2025/01/image-14.png" alt="Illustration: how you can find and debug CSS styles in the browser." class="wp-image-535" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-14.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-14-300x161.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-14-1024x549.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-14-768x412.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-14-1536x824.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-14-1320x708.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: how you can find and debug CSS styles in the browser.</figcaption></figure>



<p>CSS rules are applied in order of priority. Inline styles (applied directly to the element) usually take precedence. Overridden styles are shown as struck-through in developer tools, indicating which rule has the highest priority.</p>



<p>You can also use developer tools to copy styles from another source (as I did for Bootstrap):</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="1092" src="https://dash-resources.com/wp-content/uploads/2025/01/image-15.png" alt="lllustration: the same principle applies to copy styles from another app or source." class="wp-image-536" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-15.png 2048w, https://dash-resources.com/wp-content/uploads/2025/01/image-15-300x160.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-15-1024x546.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-15-768x410.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-15-1536x819.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-15-1320x704.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">lllustration: the same principle applies to copy styles from another app or source.</figcaption></figure>



<p>If you want to know more about this debugging process, please <a href="https://dash-resources.com/request-an-article/">send an article request</a>.</p>



<h2 id='how-to-reuse-this-code'  id="boomdevs_7" class="wp-block-heading" >How to reuse this code</h2>



<p>Each part of the styling corresponds to a specific class. This means you can copy and paste the code and simply add the relevant class name: <code>dbc</code>(for the Bootstrap style), <code>header-style</code>, <code>striped-style</code>, <code>hover-style</code> and <code>selection-style</code>.</p>



<p>Example with all styles except striped rows:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Data table
html.Div(
    dash_table.DataTable(
        # ...
    ),
    id="table_container",
    className="dbc header-style hover-style selection-style" # all styles except striped rows
)
</code></pre>



<p id="interactive-app">You can also play with the interactive app below to see the differences (or <a href="https://scripts.dash-resources.com/datatable_styling/app.py/">click here</a>):</p>



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



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary>Click here to see the final CSS code <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;" /></summary>
<pre class="wp-block-code"><code lang="css" class="language-css">/* Author: Fran from dash-resources.com 
 * Originally posted in: https://dash-resources.com/improving-dash-datatables-simple-css-tweaks/
 */

/* ------------------------------------------------*/
/* Bootstrap style */

/* reset font styles on pagination buttons */
.dbc .dash-table-container .previous-next-container .page-number,
.dbc .dash-table-container .previous-next-container .page-number .current-page-container input.current-page {
    font-family: var(--bs-body-font-family);
    font-size: var(--bs-body-font-size);
    font-weight: var(--bs-body-font-weight);
    line-height: var(--bs-body-line-height);
    min-width: auto !important;
}

/* apply the same styles as .btn, .btn-sm, .btn-outline-primary */
.dbc .dash-table-container .previous-next-container {
    button {
        &amp;.first-page,
        &amp;.last-page,
        &amp;.previous-page,
        &amp;.next-page {
            display: inline-block;
            font-weight: 400;
            text-align: center;
            vertical-align: middle;
            user-select: none;
            padding: 0.25rem 0.5rem;
            margin: 0 0.25rem;
            font-size: 0.875rem;
            line-height: 1.5;
            border-radius: 0.2rem;
            transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
            color: var(--bs-primary);
            border: 1px solid var(--bs-primary);
            background-color: transparent;

            &amp;:hover {
                color: var(--bs-white) !important; /* override dbc.css */
                background-color: var(--bs-primary);
                border-color: var(--bs-primary);
            }

            &amp;:focus {
                box-shadow: 0 0 0 0.25rem var(--bs-primary-rgb, rgba(13, 110, 253, 0.25));
            }

            &amp;:active,
            &amp;.active {
                color: var(--bs-white);
                background-color: var(--bs-primary);
                border-color: var(--bs-primary);
            }

            &amp;:disabled,
            &amp;.disabled {
                color: var(--bs-primary);
                background-color: transparent;
                border-color: var(--bs-primary);
                opacity: 0.65;
                pointer-events: none;
            }
        }
    }
}

/* ------------------------------------------------*/
/* Header style 
 * Notice we use !important to override the default style and those written in the dbc.css file
 */

/* make the heading bold and with a gray background */
.header-style .dash-table-container th {
    background-color: var(--bs-gray-200) !important;
}

.header-style .dash-table-container th .column-header-name {
    font-weight: bold;
}

.header-style .dash-table-container th input::placeholder {
    /* overrides back a style set in dbc.css */
    background-color: transparent !important;
}

/* better homogeneous styling of buttons */
.header-style .column-actions {
    /* let us breath! */
    gap: 0.4em; 
    margin-right: 0.5em;
}

.header-style .column-actions &gt; span {
    /* align components vertically */
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.header-style .dash-table-container th input[type="radio"],
.header-style .dash-table-container th input[type="checkbox"] {
    /* fix inconsistent color and size */
    width: 1rem;
    height: 1rem;
    opacity: 0.6;
}

.header-style .dash-table-container th input[type="radio"]:checked,
.header-style .dash-table-container th input[type="checkbox"]:checked {
        /* when checked, reset opacity */
    opacity: 1;
}

.header-style .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner th.dash-filter input.dash-filter--case--insensitive {
    /* avoid column width size change when clicking */
    border-style: solid;
    border-width: 2px;
    border-color: transparent;
}

/* ------------------------------------------------*/
/* Alternating row background */
.striped-style .dash-table-container td:not(.cell--selected) {
    background-color: transparent !important;
}

.striped-style .dash-table-container tr:nth-child(odd) {
    background-color: var(--bs-gray-100) !important;
}

.striped-style .dash-table-container tr:nth-child(even) {
    background-color: var(--bs-body-bg) !important;
}

/* ------------------------------------------------*/
/* Hover row background */
.hover-style .dash-table-container tr:hover td:not(.cell--selected)  {
    background-color: var(--bs-gray-200) !important;
}

/* ------------------------------------------------*/
/* Selected row style */

/* apply style to selected cell */
.selection-style .dash-table-container tr:has(td.cell--selected) td.cell--selected {
    background-color: rgba(var(--bs-primary-rgb), 0.2) !important;
    border: 1px solid var(--bs-primary) !important;
    outline: none !important;
}

/* apply styles to the other cells of selected rows */
.selection-style .dash-table-container tr:has(td.cell--selected) td {
    background-color: rgba(var(--bs-primary-rgb), 0.2) !important;
    border: 1px solid #d0d0d0 !important;
}

/* remove border between selected cells (vertically) */
.selection-style .dash-table-container tr:has(td.cell--selected) + tr:has(td.cell--selected) td.cell--selected {
    background-color: rgba(var(--bs-primary-rgb), 0.2) !important;
    border-top: none !important;
}

/* remove border between selected cells (horizontally) */
.selection-style .dash-table-container tr:has(td.cell--selected) td.cell--selected + td.cell--selected {
    border-left: none !important;
}

/* apply style to checked rows */
.selection-style .dash-table-container tr:has(input:checked) td {
    background-color: rgba(var(--bs-primary-rgb), 0.1) !important;
    border: 1px solid #d0d0d0 !important;
}

/* add dbc styles to selection-style */
body .selection-style .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner td {
    background-color: var(--bs-body-bg);
    color: var(--bs-body-color);
    border: var(--bs-border-width) solid var(--bs-border-color) !important;
}
.selection-style .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner table {
    border-collapse: inherit;
    border-spacing: unset;
}</code></pre>
</details>



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



<p>I hope this article showed how far you can go with CSS to override predefined styles. Even though it’s not perfect, small enhancements like these can significantly improve the user interface and user experience of your dashboard or app.</p>



<p>Feel free to contact me if you have suggestions for this code. <strong>Don’t forget to subscribe to get notified about new articles!</strong></p>



<p>Happy Dash 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/improving-dash-datatables-simple-css-tweaks/">Improving Dash DataTables: Simple CSS Tweaks</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 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>Dash callbacks best practices (with examples)</title>
		<link>https://dash-resources.com/dash-callbacks-best-practices-with-examples/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Mon, 30 Dec 2024 20:47:45 +0000</pubDate>
				<category><![CDATA[Intermediate level]]></category>
		<category><![CDATA[callbacks]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=203</guid>

					<description><![CDATA[<p>As a Dash app grows, it is easy to get lost in the complexity of interconnected, large callbacks handling a lot of functionality. How can [...]</p>
<p>L’article <a href="https://dash-resources.com/dash-callbacks-best-practices-with-examples/">Dash callbacks best practices (with examples)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>As a Dash app grows, it is easy to get lost in the complexity of interconnected, large callbacks handling a lot of functionality. How can you keep your app manageable? What are the best practices when it comes to Dash callbacks?</p>



<p>Over the years, I’ve developed and maintained apps either on my own or with different teams. In this tutorial, I’ll share five good practices, based on my experience, to help you better organize your Dash callbacks.</p>



<p><strong>See also:</strong> <a href="https://dash-resources.com/writing-in-process/">Dash development best practices</a></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="#1-separate-layouts-from-callbacks">1. Separate layouts from callbacks</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#example">Example</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#2-make-your-callbacks-readable">2. Make your callbacks readable</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#example-1">Example</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#3-don-t-oversplit-callbacks">3. Don’t oversplit callbacks</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#example-2">Example</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#4-avoid-big-callbacks-too">4. Avoid big callbacks too</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#example-3">Example</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#5-chain-callbacks-intelligently">5. Chain callbacks intelligently</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#example-4">Example</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#6-other-good-practices">6. Other good practices</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#avoid-using-suppress-callback-exceptions">Avoid using suppress_callback_exceptions</a>
          </li>
          <li>
            <a href="#use-prevent-initial-call-when-possible">Use prevent_initial_call when possible</a>
          </li>
          <li>
            <a href="#use-preventupdate">Use PreventUpdate</a>
          </li>
          <li class="last">
            <a href="#use-dcc-store">Use dcc.Store</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>Let’s dive in! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f604.png" alt="😄" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



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



<h2 id='1-separate-layouts-from-callbacks'  id="boomdevs_1" class="wp-block-heading" >1. Separate layouts from callbacks</h2>



<p>It’s really common to see callbacks, layouts, and data processing in the same file. Many people develop the first version of their app quickly, and it’s too early to think about organization. However, if your plan is to make the application easy to maintain, you must adopt a better project structure.</p>



<p>There are many ways to achieve this. I personally follow something that resembles the MVC pattern:</p>



<ul class="wp-block-list">
<li>Create new folders: <code>callbacks</code>, <code>layouts</code>, and <code>utils</code>.</li>



<li>Create separate files for each part of your app in the <code>callbacks</code> and <code>layouts</code> folders.</li>
</ul>



<h3 id='example'  id="boomdevs_2" class="wp-block-heading" >Example</h3>



<p>The following is an example architecture. The one that I personnaly use in my projects is very similar:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Example folder structure
app/
  ├── callbacks/
  │   ├── sales_dashboard.py    # All sales-related callbacks
  │   ├── inventory.py          # All inventory-related callbacks
  │   └── user_settings.py      # User preferences callbacks
  ├── layouts/
  │   ├── sales_dashboard.py    # Sales dashboard layout
  │   ├── inventory.py          # Inventory management layout
  │   └── user_settings.py      # Settings page layout
  ├── utils/
  │   └── data_utils.py         # Data preprocessing functions
  └── app.py</code></pre>



<p>In the <code>app.py</code> file, the code becomes more readable:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># app.py
from dash import Dash
from layouts.sales_dashboard import get_sales_dashboard_layout
from layouts.inventory import get_inventory_layout
from layouts.user_settings import get_user_settings_layout
from callbacks.sales_dashboard import *
from callbacks.inventory import *
from callbacks.user_settings import *

app = Dash(__name__)
app.layout = html.Div([
    get_sales_dashboard_layout(),
    get_inventory_layout(),
    get_user_settings_layout(),
])

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)</code></pre>



<p>With this architecture, callbacks centralize the logic of each part of the app. They rely on <code>utils</code> to load and process data and <code>layouts</code> functions to update graphs, tables, etc.</p>



<p>See below for an example callback in <code>callbacks/sales_dashboard.py</code>.</p>



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



<h2 id='2-make-your-callbacks-readable'  id="boomdevs_3" class="wp-block-heading" >2. Make your callbacks readable</h2>



<p>It’s common to start doing everything within the callback. This results in many callbacks with 10-50 lines of code, making them hard to read. A good rule of thumb is to keep a callback concise and clear.</p>



<p>You should be able to understand what the callback does just by looking at the inputs, outputs, and a few lines of code (as in the previous example).</p>



<h3 id='example-1'  id="boomdevs_4" class="wp-block-heading" >Example</h3>



<p>Here’s an example. How much time do you need to understand the following callback?</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Hard-to-read callback - too much happening
@app.callback(
    Output('sales-graph', 'figure'),
    [Input('date-picker', 'value'),
     Input('product', 'value')]
)
def update_graph(date, product):
    # Load data
    df = pd.read_csv('sales.csv')

    # Complex data processing inside callback
    df['date'] = pd.to_datetime(df['date'])
    df = df[df['date'] &gt;= date]
    df = df[df['product'] == product]

    # Calculate metrics
    total_sales = df['amount'].sum()
    avg_sale = df['amount'].mean()

    # Create figure with multiple traces
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=df['date'], y=df['amount'], name='Sales'))
    fig.add_trace(go.Scatter(x=df['date'], y=df['amount'].rolling(7).mean(), name='7-day average'))

    # Complex layout settings
    fig.update_layout(
        title=f'Sales for {product}: Total ${total_sales:,.2f}, Average ${avg_sale:.2f}',
        xaxis_title='Date',
        yaxis_title='Amount',
        hovermode='x unified'
    )
    return fig</code></pre>



<p>I&#8217;m pretty sure it took some time. Now let’s outsource each part into functions saved in other files (following advice #1):</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># callbacks/sales_dashboard.py
from layouts.sales_dashboard import create_sales_figure
from utils.data_utils import load_and_filter_data   

@app.callback(
    Output('sales-graph', 'figure'),
    [Input('date-picker', 'value'),
     Input('product', 'value')]
)
def update_graph(date, product):
    df = load_and_filter_data(date, product)
    fig = create_sales_figure(df)
    return fig</code></pre>



<p>Now, how much time did it take you to understand this code? <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f603.png" alt="😃" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>The callback function doesn&#8217;t even need to be commented. The code speaks for itself, is easily understandable, and is easy to maintain. Future developers on the project will immediately understand what is going on!</p>



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



<h2 id='3-don-t-oversplit-callbacks'  id="boomdevs_5" class="wp-block-heading" >3. Don’t oversplit callbacks</h2>



<p>I generally advocate for a “<a href="https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm">divide-and-conquer</a>” approach, which encourages making small, atomic functions. However, in the case of callbacks, it’s not always the best option. Making small callbacks increases the number of HTTP requests needed, thus increasing latency and unnecessary server rounds.</p>



<h3 id='example-2'  id="boomdevs_6" class="wp-block-heading" >Example</h3>



<p>Here’s an example. The first version splits a simple calculation into two callbacks, creating unnecessary complexity:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Unnecessarily split version
@app.callback(
    Output('temp-celcius', 'children'),
    Input('temperature', 'value')
)
def convert_to_celsius(fahrenheit):
    celsius = (fahrenheit - 32) * 5/9
    return f"{celsius:.1f}°C"

@app.callback(
    Output('temp-kelvin', 'children'),
    Input('temp-celcius', 'children')
)
def convert_to_kelvin(celsius_text):
    celsius = float(celsius_text.replace('°C', ''))
    kelvin = celsius + 273.15
    return f"{kelvin:.1f}K"</code></pre>



<p>Let’s rewrite this as a single callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Better as one callback
@app.callback(
    [Output('temp-celcius', 'children'),
     Output('temp-kelvin', 'children')],
    Input('temperature', 'value')
)
def convert_temperature(fahrenheit):
    celsius = (fahrenheit - 32) * 5/9
    kelvin = celsius + 273.15
    return f"{celsius:.1f}°C", f"{kelvin:.1f}K"</code></pre>



<p>This version combines the conversions into a single callback. It simplifies the code, eliminates the need for string parsing, and reduces the number of callback executions. The calculations are closely related and simple enough that splitting them provides no benefit.</p>



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



<h2 id='4-avoid-big-callbacks-too'  id="boomdevs_7" class="wp-block-heading" >4. Avoid big callbacks too</h2>



<p>On the other hand, making big callbacks that handle everything isn’t a good option either. But how do you know if a callback should be split or restructured?</p>



<p>A good rule of thumb is to aim for callbacks with inputs and outputs that naturally group together, i.e., share the same purpose.</p>



<h3 id='example-3'  id="boomdevs_8" class="wp-block-heading" >Example</h3>



<p>Here’s an example. This callback handles both the update of a chart and the creation of an alert:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Bad example: One callback doing too many unrelated things
@app.callback(
    [Output('sales-chart', 'figure'),
     Output('alerts', 'children')],
    [Input('date-range', 'value'),
     Input('threshold', 'value')]
)
def update_everything(date_range, threshold):
    # This callback mixes two very different responsibilities:
    # 1. Sales visualization
    # 2. Alert system for unusual patterns

    sales_data = get_sales_data(date_range)

    # Create sales visualization
    fig = create_sales_chart(sales_data)

    # Also handle complex alert logic
    unusual_patterns = detect_anomalies(sales_data, threshold)
    alert_messages = generate_alert_messages(unusual_patterns)

    return fig, alert_messages</code></pre>



<p>Now let’s split this callback into two, each with a separate purpose:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Better: Split into two callbacks with clear, separate purposes
@app.callback(
    Output('sales-chart', 'figure'),
    Input('date-range', 'value')
)
def update_sales_chart(date_range):
    # Only handles visualization
    sales_data = get_sales_data(date_range)
    return create_sales_chart(sales_data)

@app.callback(
    Output('alerts', 'children'),
    [Input('date-range', 'value'),
     Input('threshold', 'value')]
)
def update_alerts(date_range, threshold):
    # Only handles the alert system
    sales_data = get_sales_data(date_range)
    unusual_patterns = detect_anomalies(sales_data, threshold)
    return generate_alert_messages(unusual_patterns)</code></pre>



<p>Even though the original code avoids repetition and combines a common input (<code>date-range</code>), it is badly designed. Poor design will eventually complicate debugging. The split version brings the following benefits:</p>



<ul class="wp-block-list">
<li>Each callback has its own clear purpose → easier to add functionalities in the future.</li>



<li>Debugging one callback is easier as it has no impact on the other.</li>



<li>Visualization can be updated without re-triggering the alert logic, and vice versa.</li>
</ul>



<p>The key takeaway is that callbacks should be split when they handle logically separate features, not just to make them smaller.</p>



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



<h2 id='5-chain-callbacks-intelligently'  id="boomdevs_9" class="wp-block-heading" >5. Chain callbacks intelligently</h2>



<p>The way we chain callbacks in Dash Plotly can sometimes resemble the <code>goto</code> instruction in programming languages like C. The <code>goto</code> statement allows the program to jump arbitrarily from one part of the code to another, a practice <a href="https://en.wikipedia.org/wiki/Goto#Criticism">often criticized</a> for leading to &#8220;<a href="https://en.wikipedia.org/wiki/Spaghetti_code">spaghetti code</a>&#8220;—code that is difficult to debug, understand, and maintain.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1024" height="1024" src="https://dash-resources.com/wp-content/uploads/2024/12/image-3.png" alt="" class="wp-image-208" style="width:520px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2024/12/image-3.png 1024w, https://dash-resources.com/wp-content/uploads/2024/12/image-3-300x300.png 300w, https://dash-resources.com/wp-content/uploads/2024/12/image-3-150x150.png 150w, https://dash-resources.com/wp-content/uploads/2024/12/image-3-768x768.png 768w, https://dash-resources.com/wp-content/uploads/2024/12/image-3-400x400.png 400w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p>Dash&#8217;s callback system, while powerful, can introduce similar challenges if not carefully managed. As callbacks depend on specific input-output relationships, a poorly structured app can quickly become tangled and confusing, with callbacks triggering each other in complex and unintended ways.</p>



<p>Here is a list of ideas to avoid this:</p>



<ul class="wp-block-list">
<li>If you follow advice n°2, try to avoid chaining callbacks from a callback file to another callback file.</li>



<li>Avoid connecting a bottom component with a top callback. Instead, try to compute the information of this component upfront.</li>



<li>As the code changes, some inputs may not be required anymore for a callback. Removing useless inputs will reduce the overall complexity of the app. </li>



<li>Changing an <code>Input</code> to a <code>State</code> when it&#8217;s not needed can as well reduce the complexity of an app.</li>
</ul>



<h3 id='example-4'  id="boomdevs_10" class="wp-block-heading" >Example</h3>



<p>Let&#8217;s see take a look at this code : </p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Poor callback organization - "spaghetti" chaining
# callbacks_layout1.py
@app.callback(
    Output('top-chart', 'figure'),
    Input('date-picker', 'value')
)
def update_top_chart(date):
    return create_top_figure(date)

# callbacks_layout2.py
@app.callback(
    Output('middle-stats', 'children'),
    Input('top-chart', 'clickData')
)
def update_middle_stats(click_data):
    point = click_data['points'][0]
    return calculate_stats(point)

# callbacks_layout3.py
@app.callback(
    Output('bottom-table', 'data'),
    Input('middle-stats', 'children')
)
def update_bottom_table(stats):
    # Fragile: depends on parsing text from middle component
    value = float(stats.split(': ')[1])
    return get_table_data(value)</code></pre>



<p>The first version creates a chain of dependencies across different files, where each component waits for updates from the previous one. This makes the code fragile (parsing text from UI elements), hard to debug (dependencies spread across files), and inefficient (cascading updates).</p>



<p>The improved version:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Better organization - reduce dependencies, compute data upfront
# callbacks.py
def get_full_data(date):
    """Compute all required data at once"""
    df = load_data(date)
    return {
        'chart_data': prepare_chart_data(df),
        'stats': calculate_stats(df),
        'table_data': prepare_table_data(df)
    }

@app.callback(
    [Output('top-chart', 'figure'),
     Output('middle-stats', 'children'),
     Output('bottom-table', 'data')],
    Input('date-picker', 'value')
)
def update_dashboard(date):
    data = get_full_data(date)
    return (
        create_top_figure(data['chart_data']),
        format_stats(data['stats']),
        data['table_data']
    )

# If click interaction is needed, keep it separate
@app.callback(
    Output('detail-view', 'children'),
    Input('top-chart', 'clickData')
)
def show_details(click_data):
    if click_data is None:
        return "Click a point for details"</code></pre>



<p>The improved version front-loads data processing and groups related callbacks together. By streamlining dependencies and keeping data flow clear, while separating interactive features, the code becomes more maintainable.</p>



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



<h2 id='6-other-good-practices'  id="boomdevs_11" class="wp-block-heading" >6. Other good practices</h2>



<h3 id='avoid-using-suppress-callback-exceptions'  id="boomdevs_12" class="wp-block-heading" >Avoid using <code>suppress_callback_exceptions</code></h3>



<p>I think it’s not a good practice to use <code>suppress_callback_exceptions</code>. If a component is missing but shouldn’t be, this should raise an error. Explicit errors help you understand the problem more quickly. Implicit errors will waste your time.</p>



<p>If you can’t avoid setting <code>suppress_callback_exceptions=True</code>, it likely means you need to rethink your app layout. You could rely on <a>pattern-matching callbacks</a> for components created at runtime.</p>



<h3 id='use-prevent-initial-call-when-possible'  id="boomdevs_13" class="wp-block-heading" >Use <code>prevent_initial_call</code> when possible</h3>



<p>Dash executes callbacks at least once during app initialization by default. If a callback doesn’t need to be executed at first, you can disable its initial execution with <code>prevent_initial_call</code>. This reduces the number of initial callbacks triggered during loading.</p>



<h3 id='use-preventupdate'  id="boomdevs_14" class="wp-block-heading" >Use <code>PreventUpdate</code></h3>



<p><code>PreventUpdate</code> is a great way to catch errors, e.g., when a value is <code>None</code> or isn’t supposed to be what it is.</p>



<p>Using <code>PreventUpdate</code> helps stop unnecessary callback chaining, reducing the number of updates and HTTP requests. This is a performance tip, but it also simplifies debugging.</p>



<h3 id='use-dcc-store'  id="boomdevs_15" class="wp-block-heading" >Use <code>dcc.Store</code></h3>



<p>You can sometimes use <code>dcc.Store</code> to compute and store results. If the stored data is based on inputs, you instantly reduce the number of inputs needed for dependent callbacks, making them more readable.</p>



<p>However, avoid saving heavy data in <code>dcc.Store</code> (e.g., keep it under 1 MB). Large data slows down the app because it must be both downloaded and uploaded from the client.</p>



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



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-ad2f72ca wp-block-group-is-layout-flex">
<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"/>
</div>



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



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



<p>What you need to remember is that half the work of keeping callbacks easy to understand is using a well-organized structure.</p>



<ul class="wp-block-list">
<li><strong>Don’t wait too long to refactor your callback function</strong>s. If they get too big, externalize functions to make the callbacks easier to read.</li>
</ul>



<ul class="wp-block-list">
<li><strong>Don’t oversplit logic into many callbacks</strong>, <strong>but avoid processing everything in a single callback </strong>with all inputs and outputs either. Remember the general rule of thumb to create “natural groups”.</li>
</ul>



<p>Everything presented in this article isn’t an absolute truth. As the framework evolves, so do best practices. But this should help you grow your app from a simple single-page dashboard into a full interactive platform.</p>



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



<p>I hope you learned something from this article! <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;" /> Be sure to subscribe to the newsletter to see the next ones, and check out the <a href="https://dash-resources.com/dash-plotly-course/">courses</a>.</p>
<p>L’article <a href="https://dash-resources.com/dash-callbacks-best-practices-with-examples/">Dash callbacks best practices (with examples)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
