Basic Reactivity

Reactive programming

examples/basic-reactivity/greeting/app.py
from shiny import App, ui, render

app_ui = ui.page_fluid(
    ui.input_text("name", "What's your name?"),
    ui.output_text("greeting"),
)

def server(input, output, session):
    @render.text
    def greeting():
        return f"Hello {input.name()}!"
    
app = App(app_ui, server)

Reactive expressions

examples/basic-reactivity/reactive-expressions/app.py
from shiny import App, ui, render, reactive

app_ui = ui.page_fluid(
    ui.input_text("name", "What's your name?"),
    ui.output_text("greeting"),
)

def server(input, output, session):
    @reactive.calc
    def string():
        return f"Hello {input.name()}!"

    @render.text
    def greeting():
        return string()
    
app = App(app_ui, server)

Execution order

examples/basic-reactivity/execution-order/app.py
from shiny import App, ui, render, reactive

app_ui = ui.page_fluid(
    ui.input_text("name", "What's your name?"),
    ui.output_text("greeting"),
)

def server(input, output, session):
    @render.text
    def greeting():
        return string()

    @reactive.calc
    def string():
        return f"Hello {input.name()}!"

app = App(app_ui, server)

Reactive expressions

examples/basic-reactivity/two-distributions/app.py
from shiny import App, ui, render, reactive
import pandas as pd
from plotnine import ggplot, geom_freqpoly, aes, coord_cartesian
from scipy.stats import ttest_ind
import numpy as np

def freqpoly(x1, x2, binwidth=0.1, xlim=(-3, 3)):
    df = pd.DataFrame({
        "x": np.concatenate([x1, x2]),
        "g": ["x1"] * len(x1) + ["x2"] * len(x2)
    })

    res = (ggplot(df, aes("x", colour="g"))
           + geom_freqpoly(binwidth=binwidth, size=1)
           + coord_cartesian(xlim=xlim))
    
    return res

def t_test(x1, x2):
    test = ttest_ind(x1, x2)
    return f"p value: {test.pvalue:.3f}\n[{test.confidence_interval().low:.2f}, {test.confidence_interval().high:.2f}]"

app_ui = ui.page_fluid(
    ui.row(
        ui.column(4,
            "Distribution 1",
            ui.input_numeric("n1", label="n", value=1000, min=1),
            ui.input_numeric("mean1", label="µ", value=0, step=0.1),
            ui.input_numeric("sd1", label="σ", value=0.5, min=0.1, step=0.1),
        ),
         ui.column(4,
            "Distribution 2",
            ui.input_numeric("n2", label="n", value=1000, min=1),
            ui.input_numeric("mean2", label="µ", value=0, step=0.1),
            ui.input_numeric("sd2", label="σ", value=0.5, min=0.1, step=0.1),
        ),
        ui.column(4,
            "Frequency polygon",
            ui.input_numeric("binwidth", label="Bin Width", value=0.1, step=0.1),
            ui.input_slider("range", label="range", value=(-3, 3), min=-5, max=5),
        ),
    ),
    ui.row(
        ui.column(9, ui.output_plot("hist")),
        ui.column(3, ui.output_text_verbatim("ttest"))
    ),
)

def server(input, output, session):
    @reactive.calc
    def x1():
        return np.random.normal(input.mean1(), input.sd1(), size=input.n1())
    
    @reactive.calc
    def x2():
        return np.random.normal(input.mean2(), input.sd2(), size=input.n2())

    @render.plot
    def hist():
        return freqpoly(x1(), x2(), binwidth=input.binwidth(), xlim=input.range())
    
    @render.text
    def ttest():
        return t_test(x1(), x2())

app = App(app_ui, server)

Controlling timing of evaluation

Time invalidation

examples/basic-reactivity/timed-invalidation/app.py
from shiny import App, ui, render, reactive
import pandas as pd
from plotnine import ggplot, geom_freqpoly, aes, coord_cartesian
import numpy as np

def freqpoly(x1, x2, binwidth=0.1, xlim=(-3, 3)):
    df = pd.DataFrame({
        "x": np.concatenate([x1, x2]),
        "g": ["x1"] * len(x1) + ["x2"] * len(x2)
    })

    res = (ggplot(df, aes("x", colour="g"))
           + geom_freqpoly(binwidth=binwidth, size=1)
           + coord_cartesian(xlim=xlim))
    
    return res

app_ui = ui.page_fluid(
    ui.row(
        ui.column(3,
            ui.input_numeric("lambda1", "lambda1", value=3),
            ui.input_numeric("lambda2", "lambda2", value=5),
            ui.input_numeric("n", "n", value=int(1e4), min=0),
        ),
        ui.column(9, ui.output_plot("hist"))
    )
)

def server(input, output, session):
    @reactive.calc()
    def timer():
        reactive.invalidate_later(0.5)

    @reactive.calc
    def x1():
        timer()
        return np.random.poisson(input.lambda1(), size=input.n())
    
    @reactive.calc
    def x2():
        timer()
        return np.random.poisson(input.lambda2(), size=input.n())
    
    @render.plot
    def hist():
        return freqpoly(x1(), x2(), binwidth=1, xlim=(0, 40))
    
app = App(app_ui, server)

On click

examples/basic-reactivity/on-click/app.py
from shiny import App, ui, render, reactive
import pandas as pd
from plotnine import ggplot, geom_freqpoly, aes, coord_cartesian
import numpy as np

def freqpoly(x1, x2, binwidth=0.1, xlim=(-3, 3)):
    df = pd.DataFrame({
        "x": np.concatenate([x1, x2]),
        "g": ["x1"] * len(x1) + ["x2"] * len(x2)
    })

    res = (ggplot(df, aes("x", colour="g"))
           + geom_freqpoly(binwidth=binwidth, size=1)
           + coord_cartesian(xlim=xlim))
    
    return res

app_ui = ui.page_fluid(
    ui.row(
        ui.column(3,
            ui.input_numeric("lambda1", "lambda1", value=3),
            ui.input_numeric("lambda2", "lambda2", value=5),
            ui.input_numeric("n", "n", value=int(1e4), min=0),
            ui.input_action_button("simulate", "Simulate!"),
        ),
        ui.column(9, ui.output_plot("hist")),
    ),
)

def server(input, output, session):
    @reactive.calc
    @reactive.event(input.simulate, ignore_none=False)
    def x1():
        return np.random.poisson(input.lambda1(), size=input.n())
    
    @reactive.calc
    @reactive.event(input.simulate, ignore_none=False)
    def x2():
        return np.random.poisson(input.lambda2(), size=input.n())
    
    @render.plot
    def hist():
        return freqpoly(x1(), x2(), binwidth=1, xlim=(0, 40))
    
app = App(app_ui, server)
Note

Set ignore_none=False to execute the computation before the very first click on the button.

Observers

examples/basic-reactivity/observers/app.py
from shiny import App, ui, render, reactive

app_ui = ui.page_fluid(
    ui.input_text("name", "What's your name?"),
    ui.output_text("greeting"),
)

def server(input, output, session):
    @reactive.calc
    def string():
        return f"Hello {input.name()}!"

    @render.text
    def greeting():
        return string()
    
    @reactive.effect
    @reactive.event(input.name)
    def _():
        print("Greeting performed")
    
app = App(app_ui, server)