shinyvalidate - using a reactive expression witin add_rule() - r

I am trying to implement user feedback for an app I'm working on using the shinyvalidate package. Some of the feedback I want to communicate to the user is whether a value they have selected is within a specific range or not. The range of interest depends on another input they have made (I have included a simplified repex of the code).
Thus, whether the precondition of interest is met depends on a reactive value. I first define a function that checks whether a provided value is within a specified range. Then I call this function within add_rule() using a reactive as an argument to the function. This results in an error, which states that I cannot access the reactive using add_rule. This is supposedly the case because the InputValidator object is not a reactive consumer.
Error message:
Warning: Error in Can't access reactive value 'num1' outside of reactive consumer.
i Do you need to wrap inside reactive() or observer()?
55: <Anonymous>
Error: Can't access reactive value 'num1' outside of reactive consumer.
i Do you need to wrap inside reactive() or observer()?
However, if I use an unnamed function within add_rule(), I can specify a range that depends on a reactive and I no longer get the error message. The unnamed and named functions I use are identical and I don't understand why I get an error message using a named function but I do not get the error message when using the named function.
Here is my code using the named function:
library(shiny)
library(shinyvalidate)
checkRange <- function(value, value2){
if(value < -2 * value2 || value > 2 * value2 ){
paste0("Please specify a number that is within the range: ", -2 * value2, ":", 2 * value2, " - Tank you!")
}
}
ui <- fluidPage(
fluidRow(
numericInput("num1",
"Please specify your first number",
value = NULL),
numericInput("num2",
"Please specify a different number",
value = NULL),
verbatimTextOutput("selectedNums")
)
)
server <- function(input, output, session){
iv <- InputValidator$new()
iv$add_rule("num1", sv_required())
iv$add_rule("num2", sv_required())
iv$add_rule("num2", checkRange, value2 = input$num1)
iv$enable()
output$selectedNums <- renderPrint({
req(iv$is_valid())
paste0("The first number = ", input$num1, " and the second number = ", input$num2)
})
}
app <- shinyApp(ui, server)
runApp(app)
And here is the code using an anonymous function (UI and server code are largely identical except one call to iv$add_rule()):
library(shiny)
library(shinyvalidate)
ui <- fluidPage(
fluidRow(
numericInput("num1",
"Please specify your first number",
value = NULL),
numericInput("num2",
"Please specify a different number",
value = NULL),
verbatimTextOutput("selectedNums")
)
)
server <- function(input, output, session){
iv <- InputValidator$new()
iv$add_rule("num1", sv_required())
iv$add_rule("num2", sv_required())
iv$add_rule("num2", function(value){
if(value < - 2 * input$num1 || value > 2 * input$num2){
paste0("Please specify a number that is within the range: ", -2 * input$num1, ":", 2 * input$num1, " - Tank you!")
}
})
iv$enable()
output$selectedNums <- renderPrint({
req(iv$is_valid())
paste0("The first number = ", input$num1, " and the second number = ", input$num2)
})
}
app <- shinyApp(ui, server)
runApp(app)
I would prefer to use the named function since I would like to reuse the code multiple times. Could anyone help me out as to why I get an error message with the named function but not with the unnamed one?

You could do:
iv$add_rule("num2", function(value){checkRange(value, input$num1)})

Related

How to force evaluation in shiny render when generating dynamic number of elements?

I generate a dynamic number of valueBox in my shiny, and this number can change depending of the user input.
I managed to handle this with a renderUI where I put the wanted number of valueBoxOutput, and I have an observe that will feed them with the content using renderValueBox.
My problem is: the code in the renderValueBox, for some reason, is actually executed after the observe is finished, so because the renderValueBox is in a loop (to have a dynamic number of them) but the code is executed for all the output after the loop, all my output will get the last value of the loop.
Here is a min reprex:
library(shiny)
library(shinydashboard)
library(shinyWidgets)
# Function
compute <- function(id)
{
print(paste("Compute ", id))
return(id)
}
# UI
ui = shinyUI(fluidPage(
titlePanel("Compare"),
useShinydashboard(),
sidebarLayout(
sidebarPanel(
numericInput("numitems", label = "Number of items", min = 1, max = 10, value = 2)
),
mainPanel(
uiOutput("boxes")
)
)
))
# Server
server = shinyServer(function(input, output, session) {
data <- reactiveValues(
ids = list()
)
output$boxes <- renderUI({
print("boxes")
box_list <- list()
id_list <- list()
for(id in 1:(input$numitems)) {
id_box <- paste0("box_", id)
print(paste("boxes - ", id_box))
id_list <- append(id_list, id_box)
box_list <- append(
box_list,
tagList(
shinydashboard::valueBoxOutput(id_box)
)
)
data$ids <- id_list
}
print("boxes end")
fluidRow(box_list)
})
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
output[[id_box]] <- shinydashboard::renderValueBox(valueBox(id_box, compute(id_box), icon = icon("circle-info"), color = "teal"))
}
print("end observe")
})
})
# Run
shinyApp(ui = ui , server = server)
Here is the result:
And the console output:
As you can see the compute (and the render in general) is done after the end of the observe function, and both output will use the last id_box that were set (so the last loop, box_2), instead of correctly using box_1 and box_2.
I tried using force, computing valueBox outside the render, using reactive lists, nothing worked, because whatever I do the render is evaluated after the observe so only the last loop values will be used no matter what.
Do anyone know a way to force execution during the loop ? Or see another way of achieving the same result ?
Why it's always after spending hald a day on a problem, looking for dozens of posts and forum, don't find anything, finally decide to ask a question... that a few minutes later I finally find an answer.
Anyway, one way to correct this (found here) is to encapsulate the render inside the local function, like this:
observe({
print("observe")
for(id_box in data$ids) {
print(paste("observe - ", id_box))
local({
tmp <- id_box
output[[tmp]] <- shinydashboard::renderValueBox(valueBox(tmp, compute(tmp), icon = icon("circle-info"), color = "teal"))
})
}
print("end observe")
})
Now the compute is still called after the end of the observe, but the tmp variable has the correct value:
The result is what I wanted:
For the record, I had already tried to use the local function, but if you don't copy the id_box inside another variable just for the local bloc, it won't work.

R Shiny: Check condition based on reactive expressions in observeEvent

I would like to build a Shiny App with two tabs:
In one tab, some values are entered as input. In the next tab, the user can find an output that is based on the values entered in the first tab.
However, before proceeding to the output I want to check if summing up three entries will give the fourth entry.
To do so, I want to use reactive expressions that contain the values of the different entries.
Here is an example of what I would like to do:
# clean environment
rm(list = ls(all = TRUE))
library(shiny)
# Create user interface (UI)
u <- tagList(
navbarPage(
# UI for input
title = "",
id = "Example_App",
tabPanel("Model input",
fluidRow(
column(11, offset = 0,
br(),
h4("Model input"),
br(),
sidebarPanel(
div(textInput('str_Input1', 'Input 1\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
div(textInput('str_Input2', 'Input 2\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
div(textInput('str_Input3', 'Input 3\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
div(textInput('str_Input4', 'Input 4\n', "",
placeholder = "5.6, 6.7, 4.1"), class = "subheading"),
actionButton('jumpToModelOutput', 'Run')),
mainPanel(
h4('You entered'),
verbatimTextOutput("oid_Input1"),
verbatimTextOutput("oid_Input2"),
verbatimTextOutput("oid_Input3"),
verbatimTextOutput("oid_Input4"))))),
# UI for output
tabPanel("Model output",
fluidRow(
column(11, offset = 0,
br(),
h4('Your output will be here.'))
))))
# Define server output
s <- shinyServer(function(input, output, session) {
# Define reactive expressions
num_Input1 <- reactive(as.numeric(unlist(strsplit(input$str_Input1,","))))
num_Input2 <- reactive(as.numeric(unlist(strsplit(input$str_Input2,","))))
num_Input3 <- reactive(as.numeric(unlist(strsplit(input$str_Input3,","))))
num_Input4 <- reactive(as.numeric(unlist(strsplit(input$str_Input4,","))))
# Define server output for input check
output$oid_Input1 <- renderPrint({
cat("Input 1:\n")
print(num_Input1())
})
output$oid_Input2 <- renderPrint({
cat("Input 2:\n")
print(num_Input2())
})
output$oid_Input3 <- renderPrint({
cat("Input 3:\n")
print(num_Input3())
})
output$oid_Input4 <- renderPrint({
cat("Input 4:\n")
print(num_Input4())
})
# Check if conditions are fulfilled before switching to Model output
observeEvent(input$jumpToModelOutput, {
if(!all.equal((num_Input1() + num_Input2() + num_Input3()),num_Input4())){
showNotification("Error.", type = "error")
}else{
updateTabsetPanel(session, "Example_App",
selected = "Model output")
}})
})
# Create the Shiny app
shinyApp(u, s)
When I enter "1,2,3" into all tabs and press the button, the App stops and I get the following message:
"Listening on http://127.0.0.1:3925
Warning: Error in !: invalid argument type"
Removing the ! gives the following message:
Warning: Error in if: argument is not interpretable as logical
As far as I get the messages, the reactive expressions are not interpreted as numeric (?) but summing them up and printing them gives correct results.
Could anyone please help me finding the problem?
The issue is that all.equal returns a string containing a report of the difference in the passed values. That's why the docs (see ?all.equal) state:
Do not use all.equal directly in if expressions—either use isTRUE(all.equal(....)) or identical if appropriate.
Hence, to fix your issue wrap inside isTRUE:
observeEvent(input$jumpToModelOutput, {
if (!isTRUE(all.equal(num_Input1() + num_Input2() + num_Input3(), num_Input4()))) {
showNotification("Error.", type = "error")
} else {
updateTabsetPanel(session, "Example_App",
selected = "Model output"
)
}
})
all.equal returns a string if the elements are not equal, and you can't use a ! on a string. You can first check with isTRUE if it's TRUE or not and then negate it (note: you can't use isFALSE because in case it's not TRUE, all.equal returns a string). If you expect the elements to be exactly equal, you could use identical to make things easier.
I've also summed up all element in each input before adding them, is this what you wanted to do?
# Check if conditions are fulfilled before switching to Model output
observeEvent(input$jumpToModelOutput, {
if(!isTRUE(all.equal((sum(num_Input1()) + sum(num_Input2()) + sum(num_Input3())),sum(num_Input4())))){
showNotification("Error.", type = "error")
}else{
updateTabsetPanel(session, "Example_App",
selected = "Model output")
}})

How can we use `order()` or an alternative to sort a `reactiveVal` `data.frame` inside a shiny app?

I have a reactiveVal data.frame inside a shiny app that I'd like to sort. However, the attempt known from standard data.frames failed:
r <- reactiveVal(data.frame(val = c(1,3,2), name = c("Jim", "Anna", "Mouse")))
r(r()[order(r()$val, decreasing = TRUE), ])
Error in .getReactiveEnvironment()$currentContext() : Operation not
allowed without an active reactive context. (You tried to do something
that can only be done from inside a reactive expression or observer.)
A full example:
library(shiny)
ui <- fluidPage(mainPanel(textOutput("text")))
server <- function(input, output) {
r <- reactiveVal(data.frame(val = c(1,3,2), name = c("Jim", "Anna", "Mouse")))
# r(r()[order(r()$val, decreasing = TRUE), ])
output$text <- renderText(
paste0("The maximum value is ", r()$val[1],", scored by ", r()$name[1],
". The second place goes to ", r()$name[2]," with a value of ",
r()$val[2]))
}
shinyApp(ui = ui, server = server)
As the error message states:
(You tried to do something that can only be done from inside a reactive
expression or observer.)
That is, the reassignment r(r()[order(r()$val, decreasing = TRUE), ]) has to happen inside e.g. an observe or observeEvent call. This makes sense, since r is defined as a reactive value, meaning that its input is expected to change in response to certain events (e.g. clicking an action button) and we should reorder the rows of the data.frame after each input change.
If the data.frame is static in the sense that it does not change in response to an event, it does not need to be defined as a reactive value with reactiveVal and we can reorder the rows of the data.frame outside a reactive expression or observer:
server <- function(input, output) {
r <- data.frame(val = c(1,3,2), name = c("Jim", "Anna", "Mouse"))
r <- r[order(r$val, decreasing = TRUE), ]
output$text <- renderText({
paste0("The maximum value is ", r$val[1],", scored by ", r$name[1],
". The second place goes to ", r$name[2]," with a value of ",
r$val[2])
})
}
Instead of reordering r inside an observe or observeEvent call, we can also reorder the reactive value inside renderText, which also counts as a reactive expression/observer. (NB: in this case the reordered data.frame is not assigned back to the reactive value r).
server <- function(input, output) {
r <- reactiveVal(data.frame(val = c(1,3,2), name = c("Jim", "Anna", "Mouse")))
output$text <- renderText({
r_ordered <- r()[order(r()$val, decreasing = TRUE), ]
paste0("The maximum value is ", r_ordered$val[1],", scored by ", r_ordered$name[1],
". The second place goes to ", r_ordered$name[2]," with a value of ",
r_ordered$val[2])
})
}

Error in .getReactiveEnvironment()$currentContext() while using reactive output in reactiveValues function

I'm trying to get a reactiveValue that is depending on a reactive. In the real code (this is a very simplified version), I load a dataset interactively. It changes when pushing the buttons (prevBtn/nextBtn). I need to know the number of rows in the dataset, using this to plot the datapoints with different colors.
The question: Why can't I use the reactive ro() in the reactiveValues function?
For understanding: Why is the error saying "You tried to do something that can only be done from inside a reactive expression or observer.", although ro() is used inside a reactive context.
The error is definitely due to vals(), I already checked the rest.
The code :
library(shiny)
datasets <- list(mtcars, iris, PlantGrowth)
ui <- fluidPage(
mainPanel(
titlePanel("Simplified example"),
tableOutput("cars"),
actionButton("prevBtn", icon = icon("arrow-left"), ""),
actionButton("nextBtn", icon = icon("arrow-right"), ""),
verbatimTextOutput("rows")
)
)
server <- function(input, output) {
output$cars <- renderTable({
head(dat())
})
dat <- reactive({
if (is.null(rv$nr)) {
d <- mtcars
}
else{
d <- datasets[[rv$nr]]
}
})
rv <- reactiveValues(nr = 1)
set_nr <- function(direction) {
rv$nr <- rv$nr + direction
}
observeEvent(input$nextBtn, {
set_nr(1)
})
observeEvent(input$prevBtn, {
set_nr(-1)
})
ro <- reactive({
nrow(dat())
})
output$rows <- renderPrint({
print(paste(as.character(ro()), "rows"))
})
vals <- reactiveValues(needThisForLater = 30 * ro())
}
shinyApp(ui = ui, server = server)
Error in .getReactiveEnvironment()$currentContext() :
Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
I think you want
vals <- reactiveValues(needThisForLater = reactive(30 * ro()))
Not everything in a reactiveValues list is assumed to be reactive. It's also a good place to store constant values. So since it's trying to evaluate the parameter you are passing at run time and you are not calling that line in a reactive environment, you get that error. So by just wrapping it in a call to reactive(), you provide a reactive environment for ro() to be called in.

Passing reactive values to functions and accessing reactive attributes

I want to take a reactive source through more than one reactive conductor before sending it to a reactive endpoint.
Generalized example:
x <- reactive({ function(input$var) })
y <-reactive({ function(x) })
output$display<-renderText({ y()$attr })
Below is a specific Example.
Thanks in advance
# Install packages if needed
packageNeeds <- c('shiny', 'httr', 'dataRetrieval')
packageNeeds <- packageNeeds[!packageNeeds %in% rownames(installed.packages())]
if(length(packageNeeds)>0){
install.packages(packageNeeds, repos='http://cran.cnr.Berkeley.edu')
}
library(shiny)
library(httr)
library(dataRetrieval)
shinyApp(
ui = fluidPage(
fluidRow(
wellPanel(
selectInput("site", label = p("Pick a site"), choices = list('01594440', 'sysys')),
selectInput("start", label = p("pick a date"), choices = list('1985-01-01', "xyz")))),
fluidRow(wellPanel(verbatimTextOutput("URL"))),
fluidRow(wellPanel(verbatimTextOutput("REC")))),
server = function(input, output, session){
url<-reactive({
# Here the user inputs are used to constuct a url -
# but really this could be any function that I'd like to take the output of
# and pass it on to other functions and/or subeset
# before sending to a reactive output
constructWQPURL(paste("USGS",input$site,sep="-"),
c('01075','00029','00453'),
input$start, endDate = "")
})
# This is the function I've passed the reactive value to
# it returns an object with a headers attributes that has the
# 'total-result-count' attribute I want
header_call = reactive({HEAD(url)})
output$URL<-renderText({
# The url displays fine
url()
})
output$REC<-renderText({
# The record count from the header pull does not display
# the value is the number of records stored as a string
header_call()$headers$'total-result-count'
})
}
)
One issue might be that you are missing parenthesis after url when you define header_call. url is a closure, url() returns the URL string.
Also renderText is reactive, so you can just call HEAD(url()) from there:
This works for me (I removed header_call = reactive({HEAD(url)})) :
output$REC<-renderText({
# The record count from the header pull does not display
# the value is the number of records stored as a string
#header_call()
HEAD(url())$headers$'total-result-count'
})

Resources