Shiny modules
Shiny modules allow you to create re-usable application components.
From app to shiny module
Let’s look at a simple shiny app that implements a counter.
#| standalone: true
#| components: [editor, viewer]
from shiny import App, reactive, render, ui
custom_label = "Some Counter"
counter_ui = ui.div(
{"style": "border: 1px solid #ccc; border-radius: 5px; margin: 5px 0;"},
ui.h2("This is " + custom_label),
ui.input_action_button(id="button", label=custom_label),
ui.output_text_verbatim(id="out"),
)
def counter_server(input, output, session, starting_value = 0):
count = reactive.Value(starting_value)
@reactive.Effect
@reactive.event(input.button)
def _():
count.set(count() + 1)
@output
@render.text
def out():
return f"Click count is {count()}"
app = App(counter_ui, counter_server)
How can we make this reproducible, so people can include counters in their own apps?
The key is to do the following:
- wrap the UI piece in a function, and decorate it with
module.ui
. - decorate the server function with
module.server
.
Here is the UI from the example above turned into a module.
from shiny import module, ui, render, reactive, event, App
# UI ----
# Note that we made conter_ui a function, and decorated it
@module.ui
def counter_ui(custom_label: str = "Increment counter"):
return ui.div(
"style": "border: 1px solid #ccc; border-radius: 5px; margin: 5px 0;"},
{"This is " + custom_label),
ui.h2(id="button", label=custom_label),
ui.input_action_button(id="out"),
ui.output_text_verbatim( )
Notice that now the UI is a function that can take the parameter custom_label
.
Now let’s look at the changes to the server function, which just needs to be decorated with @module.server
.
# Server ----
# Note that we just added the @module.server decorator
@module.server
def counter_server(input, output, session, starting_value = 0):
= reactive.Value(starting_value)
count
@reactive.Effect
@reactive.event(input.button)
def _():
set(count() + 1)
count.
@output
@render.text
def out():
return f"Click count is {count()}"
Using modules in an app
The example below shows how an app can use the module above (which is in the counter.py tab).
#| standalone: true
#| components: [editor, viewer]
## file: app.py
from shiny import App, ui
from .counter import counter_ui, counter_server
app_ui = ui.page_fluid(
counter_ui("counter1", "Counter 1"),
counter_ui("counter2", "Counter 2"),
)
def server(input, output, session):
counter_server("counter1")
counter_server("counter2")
app = App(app_ui, server)
## file: counter.py
from shiny import module, ui, render, reactive, event
@module.ui
def counter_ui(label: str = "Increment counter"):
return ui.div(
{"style": "border: 1px solid #ccc; border-radius: 5px; margin: 5px 0;"},
ui.h2("This is " + label),
ui.input_action_button(id="button", label=label),
ui.output_text_verbatim(id="out"),
)
@module.server
def counter_server(input, output, session, starting_value = 0):
count = reactive.Value(starting_value)
@reactive.Effect
@reactive.event(input.button)
def _():
count.set(count() + 1)
@output
@render.text
def out():
return f"Click count is {count()}"