diff --git a/doc/how_to/performance/hold.md b/doc/how_to/performance/hold.md index 3c49250e15..81d55ed400 100644 --- a/doc/how_to/performance/hold.md +++ b/doc/how_to/performance/hold.md @@ -1,18 +1,34 @@ -# 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 @@ -20,20 +36,20 @@ 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 @@ -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.