0
# Reactive Programming
1
2
Shiny's reactive programming system automatically tracks dependencies and updates outputs when inputs change. The reactive system includes reactive values, calculations, effects, and event handling that form the core of Shiny's reactivity model.
3
4
## Capabilities
5
6
### Reactive Values
7
8
Reactive values are the foundation of Shiny's reactive system, storing data that can trigger updates when changed.
9
10
```python { .api }
11
def reactive.value(value: T) -> Value[T]:
12
"""
13
Create a reactive value.
14
15
Args:
16
value: Initial value of any type.
17
18
Returns:
19
Value object that can be read and written reactively.
20
"""
21
22
class reactive.Value[T]:
23
"""
24
A reactive value that can be read and written.
25
"""
26
def __call__(self) -> T:
27
"""Get the current value."""
28
29
def set(self, value: T) -> None:
30
"""Set a new value, triggering reactive updates."""
31
32
def freeze(self) -> T:
33
"""Get value without establishing reactive dependency."""
34
35
def is_set(self) -> bool:
36
"""Check if value has been set."""
37
```
38
39
#### Usage Examples
40
41
```python
42
from shiny import reactive
43
44
# Create reactive values
45
counter = reactive.value(0)
46
user_name = reactive.value("")
47
selected_data = reactive.value(None)
48
49
# Reading reactive values (establishes dependency)
50
current_count = counter() # Will trigger updates when counter changes
51
name = user_name() # Will trigger updates when user_name changes
52
53
# Setting reactive values (triggers dependent updates)
54
counter.set(counter() + 1) # Increment counter
55
user_name.set("Alice") # Update name
56
selected_data.set(df.iloc[0:10]) # Update data selection
57
58
# Non-reactive reading (no dependency established)
59
current_count_frozen = counter.freeze()
60
61
# Check if value has been set
62
if selected_data.is_set():
63
process_data(selected_data())
64
```
65
66
### Reactive Calculations
67
68
Reactive calculations are cached computations that automatically update when their dependencies change.
69
70
```python { .api }
71
def reactive.calc(fn: Callable[[], T]) -> Calc[T]:
72
"""
73
Create a reactive calculation.
74
75
Args:
76
fn: Function that computes the value. Should not have side effects.
77
78
Returns:
79
Calc object that caches the computed value.
80
"""
81
82
class reactive.Calc[T]:
83
"""
84
A reactive calculation that caches computed values.
85
"""
86
def __call__(self) -> T:
87
"""Get the computed value, recalculating if dependencies changed."""
88
89
def invalidate(self) -> None:
90
"""Invalidate the cached value, forcing recalculation."""
91
```
92
93
#### Usage Examples
94
95
```python
96
# Create reactive calculations
97
@reactive.calc
98
def filtered_data():
99
df = input.dataset()
100
filter_val = input.filter_value()
101
return df[df['column'] > filter_val] # Automatically updates when inputs change
102
103
@reactive.calc
104
def summary_stats():
105
data = filtered_data() # Depends on filtered_data calculation
106
return {
107
'mean': data['value'].mean(),
108
'std': data['value'].std(),
109
'count': len(data)
110
}
111
112
# Use calculations in outputs
113
@output
114
@render.text
115
def stats_display():
116
stats = summary_stats() # Will update when underlying data changes
117
return f"Mean: {stats['mean']:.2f}, Count: {stats['count']}"
118
119
# Alternative syntax without decorator
120
processed_data = reactive.calc(lambda: expensive_computation(input.data()))
121
```
122
123
### Reactive Effects
124
125
Reactive effects perform side effects when their dependencies change, but don't return values.
126
127
```python { .api }
128
def reactive.effect(fn: Callable[[], None]) -> Effect:
129
"""
130
Create a reactive effect.
131
132
Args:
133
fn: Function that performs side effects. Should not return a value.
134
135
Returns:
136
Effect object for managing the effect.
137
"""
138
139
class reactive.Effect:
140
"""
141
A reactive effect that performs side effects.
142
"""
143
def destroy(self) -> None:
144
"""Stop the effect from running."""
145
146
def invalidate(self) -> None:
147
"""Invalidate the effect, causing it to re-run."""
148
```
149
150
#### Usage Examples
151
152
```python
153
# Create reactive effects
154
@reactive.effect
155
def update_database():
156
# Runs whenever selected_data changes
157
data = selected_data()
158
if data is not None:
159
database.save_selection(data)
160
161
@reactive.effect
162
def log_user_activity():
163
# Log when user changes inputs
164
current_page = input.page()
165
user_id = session.get_user_id()
166
logger.info(f"User {user_id} viewed page {current_page}")
167
168
# Manual effect creation
169
def cleanup_temp_files():
170
temp_dir = input.temp_directory()
171
if temp_dir and os.path.exists(temp_dir):
172
shutil.rmtree(temp_dir)
173
174
cleanup_effect = reactive.effect(cleanup_temp_files)
175
176
# Destroy effect when no longer needed
177
# cleanup_effect.destroy()
178
```
179
180
### Event Handling
181
182
Event-based reactivity for controlling when reactive code should run.
183
184
```python { .api }
185
def reactive.event(
186
*args: object,
187
ignore_none: bool = True,
188
ignore_init: bool = False
189
) -> Callable[[Callable[[], T]], Callable[[], T]]:
190
"""
191
Decorator for event-driven reactivity.
192
193
Args:
194
*args: Event sources (typically input values or reactive values).
195
ignore_none: Don't trigger on None values.
196
ignore_init: Don't trigger on initial evaluation.
197
198
Returns:
199
Decorator function.
200
"""
201
```
202
203
#### Usage Examples
204
205
```python
206
# Event-driven calculations
207
@reactive.calc
208
@reactive.event(input.update_button)
209
def expensive_analysis():
210
# Only runs when update button is clicked, not when data changes
211
return perform_complex_analysis(input.dataset())
212
213
@output
214
@render.plot
215
@reactive.event(input.plot_button, ignore_init=True)
216
def generate_plot():
217
# Only generates plot when button is clicked
218
data = input.data()
219
return create_visualization(data)
220
221
# Multiple event sources
222
@reactive.calc
223
@reactive.event(input.refresh_btn, input.auto_refresh)
224
def updated_data():
225
# Runs when either refresh button clicked OR auto_refresh changes
226
return fetch_latest_data()
227
228
# Event-driven effects
229
@reactive.effect
230
@reactive.event(input.save_button)
231
def save_data():
232
# Save only when save button is clicked
233
current_data = get_current_state()
234
database.save(current_data)
235
```
236
237
### Extended Tasks
238
239
Long-running asynchronous tasks that can be monitored and controlled.
240
241
```python { .api }
242
class reactive.ExtendedTask:
243
"""
244
A task that can run for an extended period with status tracking.
245
"""
246
def __init__(self, func: Callable[..., Awaitable[T]]): ...
247
248
def __call__(self, *args: object, **kwargs: object) -> ExtendedTask[T]: ...
249
250
def cancel(self) -> None:
251
"""Cancel the running task."""
252
253
def result(self) -> T:
254
"""Get the task result (blocks until complete)."""
255
256
def status(self) -> Literal["initial", "running", "success", "error", "cancelled"]:
257
"""Get current task status."""
258
259
def reactive.extended_task(
260
func: Callable[..., Awaitable[T]]
261
) -> ExtendedTask[T]:
262
"""
263
Decorator to create an extended task.
264
265
Args:
266
func: Async function to run as extended task.
267
268
Returns:
269
ExtendedTask wrapper.
270
"""
271
```
272
273
#### Usage Examples
274
275
```python
276
import asyncio
277
from shiny import reactive
278
279
# Define extended task
280
@reactive.extended_task
281
async def process_large_dataset(dataset_path, parameters):
282
# Long-running data processing
283
await asyncio.sleep(1) # Simulate work
284
285
with open(dataset_path, 'r') as f:
286
data = load_and_process(f, parameters)
287
288
# More processing...
289
await asyncio.sleep(2)
290
291
return analysis_results
292
293
# Use in server function
294
def server(input: Inputs, output: Outputs, session: Session):
295
296
@reactive.effect
297
@reactive.event(input.start_processing)
298
def start_task():
299
# Start the extended task
300
process_large_dataset(
301
input.dataset_file(),
302
input.processing_params()
303
)
304
305
@output
306
@render.text
307
def task_status():
308
# Show current status
309
status = process_large_dataset.status()
310
if status == "running":
311
return "Processing... Please wait."
312
elif status == "success":
313
return "Processing complete!"
314
elif status == "error":
315
return "An error occurred during processing."
316
else:
317
return "Ready to start processing."
318
319
@output
320
@render.table
321
def results():
322
# Show results when task completes
323
if process_large_dataset.status() == "success":
324
return process_large_dataset.result()
325
return None
326
327
@reactive.effect
328
@reactive.event(input.cancel_button)
329
def cancel_task():
330
process_large_dataset.cancel()
331
```
332
333
### Time-Based Reactivity
334
335
Utilities for time-based reactive updates and polling.
336
337
```python { .api }
338
def reactive.invalidate_later(
339
delay: float,
340
session: Session | None = None
341
) -> None:
342
"""
343
Schedule reactive invalidation after a delay.
344
345
Args:
346
delay: Delay in seconds.
347
session: Session to use (current session if None).
348
"""
349
350
def reactive.poll(
351
func: Callable[[], T],
352
interval_secs: float,
353
session: Session | None = None
354
) -> Callable[[], T]:
355
"""
356
Poll a function at regular intervals.
357
358
Args:
359
func: Function to poll.
360
interval_secs: Polling interval in seconds.
361
session: Session to use (current session if None).
362
363
Returns:
364
Reactive function that returns polled value.
365
"""
366
367
def reactive.file_reader(
368
filepath: str | os.PathLike[str],
369
session: Session | None = None
370
) -> Callable[[], str | None]:
371
"""
372
Reactively read a file when it changes.
373
374
Args:
375
filepath: Path to file to monitor.
376
session: Session to use (current session if None).
377
378
Returns:
379
Function that returns file contents when changed.
380
"""
381
```
382
383
#### Usage Examples
384
385
```python
386
# Auto-refresh data every 30 seconds
387
@reactive.calc
388
def live_data():
389
reactive.invalidate_later(30) # Refresh every 30 seconds
390
return fetch_current_data()
391
392
# Poll external API
393
api_data = reactive.poll(
394
lambda: requests.get('https://api.example.com/data').json(),
395
interval_secs=60 # Poll every minute
396
)
397
398
@output
399
@render.text
400
def current_api_data():
401
data = api_data()
402
return f"Latest data: {data}"
403
404
# Monitor log file
405
log_contents = reactive.file_reader("/var/log/myapp.log")
406
407
@output
408
@render.text
409
def log_display():
410
contents = log_contents()
411
if contents:
412
# Show last 10 lines
413
return "\n".join(contents.strip().split("\n")[-10:])
414
return "No log data"
415
```
416
417
### Reactive Context Management
418
419
Low-level utilities for managing reactive contexts.
420
421
```python { .api }
422
def reactive.isolate(fn: Callable[[], T]) -> T:
423
"""
424
Execute function without establishing reactive dependencies.
425
426
Args:
427
fn: Function to execute in isolation.
428
429
Returns:
430
Function result.
431
"""
432
433
def reactive.flush() -> None:
434
"""
435
Force the reactive system to flush all pending updates.
436
"""
437
438
def reactive.get_current_context() -> Context | None:
439
"""
440
Get the current reactive context.
441
442
Returns:
443
Current Context object or None if not in reactive context.
444
"""
445
446
class reactive.Context:
447
"""
448
Reactive execution context.
449
"""
450
def __enter__(self) -> Context: ...
451
def __exit__(self, *args: object) -> None: ...
452
```
453
454
#### Usage Examples
455
456
```python
457
# Execute code without creating reactive dependencies
458
@reactive.calc
459
def smart_calculation():
460
# This will create a reactive dependency
461
primary_input = input.primary_value()
462
463
# This won't create a reactive dependency
464
config_value = reactive.isolate(lambda: input.config_setting())
465
466
# Calculation only re-runs when primary_value changes,
467
# not when config_setting changes
468
return perform_calculation(primary_input, config_value)
469
470
# Force reactive flush for testing or debugging
471
def update_all_outputs():
472
# Make some changes
473
my_reactive_value.set("new value")
474
another_value.set(42)
475
476
# Force immediate update of all dependent calculations
477
reactive.flush()
478
479
# Now all outputs are guaranteed to be current
480
481
# Work with reactive context
482
context = reactive.get_current_context()
483
if context:
484
# We're in a reactive context
485
print("Currently in reactive execution")
486
```