Batching Updates with hold
#
When working with interactive dashboards and applications in Panel, you might encounter situations where updating multiple components simultaneously causes unnecessary re-renders. This is because Panel generally dispatches any change to a parameter immediately. This can lead to performance issues and a less responsive user experience because each individual update may trigger re-renders on the frontend. The hold
utility in Panel allows you to batch updates to the frontend, reducing the number of re-renders and improving performance.
In this guide, we’ll explore how to use hold
both as a context manager and as a decorator to optimize your Panel applications.
What is hold
?#
The hold
function is a context manager and decorator that temporarily holds events on a Bokeh Document. When you update multiple components within a hold
block, the events are collected and dispatched all at once when the block exits. This means that the frontend will only re-render once, regardless of how many updates were made, leading to a smoother and more efficient user experience.
Let’s try first without hold
to understand the difference hold
can make:
import panel as pn
from panel.io import hold
def increment(e):
for obj in column_0:
obj.object = str(e.new)
column_0 = pn.FlexBox(*['0']*100)
button = pn.widgets.Button(name='Increment', on_click=increment)
pn.Column(column_0, button).servable()
Using hold
#
As a Decorator#
If you have a function that updates components and you want to ensure that all updates are held, you can use hold
as a decorator. For example, here we update 100 components at once. If you do not use hold
, each of these events is sent and applied in series, potentially resulting in visible updates.
import panel as pn
from panel.io import hold
@hold()
def increment(e):
for obj in column_1:
obj.object = str(e.new)
column_1 = pn.FlexBox(*['0']*100)
button = pn.widgets.Button(name='Increment', on_click=increment)
pn.Column(column_1, button).servable()
Applying the hold
decorator means all the updates are sent in a single WebSocket message and applied on the frontend simultaneously.
As a Context Manager#
Alternatively, the hold
function can be used as a context manager, potentially giving you finer-grained control over which events are batched and which are not:
import time
import panel as pn
from panel.io import hold
def increment(e):
with button.param.update(name='Incrementing...', disabled=True):
with hold():
for obj in column_2:
obj.object = str(e.new)
column_2 = pn.FlexBox(*['0']*100)
button = pn.widgets.Button(name='Increment', on_click=increment)
pn.Column(column_2, button).servable()
Here the updates to the Button
are dispatched immediately, while the updates to the counters are batched.