Skip to content

Commit

Permalink
improve hold docs (#7500)
Browse files Browse the repository at this point in the history
Co-authored-by: Philipp Rudiger <[email protected]>
  • Loading branch information
MarcSkovMadsen and philippjfr authored Nov 18, 2024
1 parent a248460 commit 8421976
Showing 1 changed file with 32 additions and 17 deletions.
49 changes: 32 additions & 17 deletions doc/how_to/performance/hold.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,55 @@
# Batching updates with `hold`
# 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.
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?
## 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.
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:

```{pyodide}
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
### 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. E.g. here we update 100 components at once. If you do not hold then each of these events is sent and applied in series, potentially resulting in visible updates.
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.

```{pyodide}
import panel as pn
from panel.io import hold
@hold()
def increment(e):
for obj in column:
for obj in column_1:
obj.object = str(e.new)
column = pn.FlexBox(*['0']*100)
column_1 = pn.FlexBox(*['0']*100)
button = pn.widgets.Button(name='Increment', on_click=increment)
pn.Column(column, button).servable()
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.
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
### 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:
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:

```{pyodide}
import time
Expand All @@ -43,15 +59,14 @@ from panel.io import hold
def increment(e):
with button.param.update(name='Incrementing...', disabled=True):
time.sleep(0.5)
with hold():
for obj in column:
for obj in column_2:
obj.object = str(e.new)
column = pn.FlexBox(*['0']*100)
column_2 = pn.FlexBox(*['0']*100)
button = pn.widgets.Button(name='Increment', on_click=increment)
pn.Column(column, button).servable()
pn.Column(column_2, button).servable()
```

Here the updates to the `Button` are dispatched immediately while the updates to the counters are batched.
Here the updates to the `Button` are dispatched immediately, while the updates to the counters are batched.

0 comments on commit 8421976

Please sign in to comment.