Skip to content

Commit

Permalink
fix duplicated FilterState when reloading (#609)
Browse files Browse the repository at this point in the history
Fix Bug found by @vedhav when reloading data.
  • Loading branch information
gogonzo authored Aug 8, 2024
1 parent 7010a81 commit df87600
Show file tree
Hide file tree
Showing 18 changed files with 148 additions and 108 deletions.
46 changes: 19 additions & 27 deletions R/FilterState.R
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ FilterState <- R6::R6Class( # nolint
private$server_inputs("inputs")
}

private$observers[[session$ns("state")]] <- observeEvent(
private$session_bindings[[session$ns("state")]] <- observeEvent(
eventExpr = list(private$get_selected(), private$get_keep_na(), private$get_keep_inf()),
handlerExpr = {
current_state <- as.list(self$get_state())
Expand All @@ -225,7 +225,7 @@ FilterState <- R6::R6Class( # nolint
}
)

private$observers[[session$ns("back")]] <- observeEvent(
private$session_bindings[[session$ns("back")]] <- observeEvent(
eventExpr = input$back,
handlerExpr = {
history <- rev(private$state_history())
Expand All @@ -236,7 +236,7 @@ FilterState <- R6::R6Class( # nolint
}
)

private$observers[[session$ns("reset")]] <- observeEvent(
private$session_bindings[[session$ns("reset")]] <- observeEvent(
eventExpr = input$reset,
handlerExpr = {
slice <- private$state_history()[[1L]]
Expand All @@ -246,7 +246,7 @@ FilterState <- R6::R6Class( # nolint

# Buttons for rewind/reset are disabled upon change in history to prevent double-clicking.
# Re-enabling occurs after 100 ms, after they are potentially hidden when no history is present.
private$observers[[session$ns("state_history")]] <- observeEvent(
private$session_bindings[[session$ns("state_history")]] <- observeEvent(
eventExpr = private$state_history(),
handlerExpr = {
shinyjs::disable(id = "back")
Expand All @@ -268,25 +268,21 @@ FilterState <- R6::R6Class( # nolint
}
)

private$observers[[session$ns("remove")]] <- observeEvent(
private$session_bindings[[session$ns("remove")]] <- observeEvent(
once = TRUE, # remove button can be called once, should be destroyed afterwards
ignoreInit = TRUE, # ignoreInit: should not matter because we destroy the previous input set of the UI
eventExpr = input$remove, # when remove button is clicked in the FilterState ui
handlerExpr = remove_callback()
)

private$destroy_shiny <- function() {
logger::log_debug("Destroying FilterState inputs and observers; id: { private$get_id() }")

if (session$isEnded()) {
return(NULL)
} # skip input removal if session has ended
# remove values from the input list
lapply(session$ns(names(input)), .subset2(input, "impl")$.values$remove)

# remove observers
lapply(private$observers, function(x) x$destroy())
}
private$session_bindings[[session$ns("inputs")]] <- list(
destroy = function() {
logger::log_debug("Destroying FilterState inputs and observers; id: { private$get_id() }")
if (!session$isEnded()) {
lapply(session$ns(names(input)), .subset2(input, "impl")$.values$remove)
}
}
)

private$state_history <- reactiveVal(list())

Expand Down Expand Up @@ -388,17 +384,14 @@ FilterState <- R6::R6Class( # nolint
},

#' @description
#' Destroy observers stored in `private$observers`.
#' Destroy inputs and observers stored in `private$session_bindings`.
#'
#' The `destroy_shiny` definition is set in the server method.
#'
#' @return `NULL`, invisibly.
#'
finalize = function() {
if (!is.null(private$destroy_shiny)) {
private$destroy_shiny()
private$destroy_shiny <- NULL
}
.finalize_session_bindings(self, private)
invisible(NULL)
}
),

Expand All @@ -412,10 +405,9 @@ FilterState <- R6::R6Class( # nolint
na_count = integer(0),
filtered_na_count = NULL, # reactive containing the count of NA in the filtered dataset
varlabel = character(0), # taken from variable labels in data; displayed in filter cards
destroy_shiny = NULL, # function is set in server
# other
is_choice_limited = FALSE, # flag whether number of possible choices was limited when specifying filter
observers = list(), # stores observers
session_bindings = list(), # stores observers and inputs to destroy afterwards
state_history = NULL, # reactiveVal holding a list storing states this FilterState has had since instantiation

# private methods ----
Expand Down Expand Up @@ -771,7 +763,7 @@ FilterState <- R6::R6Class( # nolint
# this observer is needed in the situation when private$keep_inf has been
# changed directly by the api - then it's needed to rerender UI element
# to show relevant values
private$observers[[session$ns("keep_na_api")]] <- observeEvent(
private$session_bindings[[session$ns("keep_na_api")]] <- observeEvent(
ignoreNULL = FALSE, # nothing selected is possible for NA
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
eventExpr = private$get_keep_na(),
Expand All @@ -786,7 +778,7 @@ FilterState <- R6::R6Class( # nolint
}
}
)
private$observers[[session$ns("keep_na")]] <- observeEvent(
private$session_bindings[[session$ns("keep_na")]] <- observeEvent(
ignoreNULL = FALSE, # ignoreNULL: we don't want to ignore NULL when nothing is selected in the `selectInput`
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
eventExpr = input$value,
Expand Down
6 changes: 2 additions & 4 deletions R/FilterStateChoices.R
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,6 @@ ChoicesFilterState <- R6::R6Class( # nolint
id = id,
function(input, output, session) {
logger::log_debug("ChoicesFilterState$server_inputs initializing, id: { private$get_id() }")

# 1. renderUI is used here as an observer which triggers only if output is visible
# and if the reactive changes - reactive triggers only if the output is visible.
# 2. We want to trigger change of the labels only if reactive count changes (not underlying data)
Expand Down Expand Up @@ -454,7 +453,7 @@ ChoicesFilterState <- R6::R6Class( # nolint
})
})

private$observers[[session$ns("selection")]] <- if (private$is_checkboxgroup()) {
private$session_bindings[[session$ns("selection")]] <- if (private$is_checkboxgroup()) {
observeEvent(
ignoreNULL = FALSE,
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
Expand Down Expand Up @@ -504,13 +503,12 @@ ChoicesFilterState <- R6::R6Class( # nolint
)
}


private$keep_na_srv("keep_na")

# this observer is needed in the situation when teal_slice$selected has been
# changed directly by the api - then it's needed to rerender UI element
# to show relevant values
private$observers[[session$ns("selection_api")]] <- observeEvent(private$get_selected(), {
private$session_bindings[[session$ns("selection_api")]] <- observeEvent(private$get_selected(), {
# it's important to not retrigger when the input$selection is the same as reactive values
# kept in the teal_slice$selected
if (!setequal(input$selection, private$get_selected())) {
Expand Down
8 changes: 4 additions & 4 deletions R/FilterStateDate.R
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ DateFilterState <- R6::R6Class( # nolint
# this observer is needed in the situation when teal_slice$selected has been
# changed directly by the api - then it's needed to rerender UI element
# to show relevant values
private$observers[[session$ns("selection_api")]] <- observeEvent(
private$session_bindings[[session$ns("selection_api")]] <- observeEvent(
ignoreNULL = TRUE, # dates needs to be selected
ignoreInit = TRUE,
eventExpr = private$get_selected(),
Expand All @@ -344,7 +344,7 @@ DateFilterState <- R6::R6Class( # nolint
}
)

private$observers[[session$ns("selection")]] <- observeEvent(
private$session_bindings[[session$ns("selection")]] <- observeEvent(
ignoreNULL = TRUE, # dates needs to be selected
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
eventExpr = input$selection,
Expand Down Expand Up @@ -374,7 +374,7 @@ DateFilterState <- R6::R6Class( # nolint

private$keep_na_srv("keep_na")

private$observers[[session$ns("reset1")]] <- observeEvent(input$start_date_reset, {
private$session_bindings[[session$ns("reset1")]] <- observeEvent(input$start_date_reset, {
logger::log_debug("DateFilterState$server@3 reset start date, id: { private$get_id() }")
updateDateRangeInput(
session = session,
Expand All @@ -383,7 +383,7 @@ DateFilterState <- R6::R6Class( # nolint
)
})

private$observers[[session$ns("reset2")]] <- observeEvent(input$end_date_reset, {
private$session_bindings[[session$ns("reset2")]] <- observeEvent(input$end_date_reset, {
logger::log_debug("DateFilterState$server@4 reset end date, id: { private$get_id() }")
updateDateRangeInput(
session = session,
Expand Down
10 changes: 5 additions & 5 deletions R/FilterStateDatettime.R
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ DatetimeFilterState <- R6::R6Class( # nolint
# this observer is needed in the situation when teal_slice$selected has been
# changed directly by the api - then it's needed to rerender UI element
# to show relevant values
private$observers[[session$ns("selection_api")]] <- observeEvent(
private$session_bindings[[session$ns("selection_api")]] <- observeEvent(
ignoreNULL = TRUE, # dates needs to be selected
ignoreInit = TRUE, # on init selected == default, so no need to trigger
eventExpr = private$get_selected(),
Expand Down Expand Up @@ -417,7 +417,7 @@ DatetimeFilterState <- R6::R6Class( # nolint
)


private$observers[[session$ns("selection_start")]] <- observeEvent(
private$session_bindings[[session$ns("selection_start")]] <- observeEvent(
ignoreNULL = TRUE, # dates needs to be selected
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
eventExpr = input$selection_start,
Expand Down Expand Up @@ -445,7 +445,7 @@ DatetimeFilterState <- R6::R6Class( # nolint
}
)

private$observers[[session$ns("selection_end")]] <- observeEvent(
private$session_bindings[[session$ns("selection_end")]] <- observeEvent(
ignoreNULL = TRUE, # dates needs to be selected
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
eventExpr = input$selection_end,
Expand Down Expand Up @@ -475,7 +475,7 @@ DatetimeFilterState <- R6::R6Class( # nolint

private$keep_na_srv("keep_na")

private$observers[[session$ns("reset1")]] <- observeEvent(
private$session_bindings[[session$ns("reset1")]] <- observeEvent(
ignoreInit = TRUE, # reset button shouldn't be trigger on init
ignoreNULL = TRUE, # it's impossible and wrong to set default to NULL
input$start_date_reset,
Expand All @@ -488,7 +488,7 @@ DatetimeFilterState <- R6::R6Class( # nolint
logger::log_debug("DatetimeFilterState$server@2 reset start date, id: { private$get_id() }")
}
)
private$observers[[session$ns("reset2")]] <- observeEvent(
private$session_bindings[[session$ns("reset2")]] <- observeEvent(
ignoreInit = TRUE, # reset button shouldn't be trigger on init
ignoreNULL = TRUE, # it's impossible and wrong to set default to NULL
input$end_date_reset,
Expand Down
30 changes: 12 additions & 18 deletions R/FilterStateExpr.R
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,12 @@ FilterStateExpr <- R6::R6Class( # nolint
},

#' @description
#' Destroy observers stored in `private$observers`.
#' Destroy inputs and observers stored in `private$session_bindings`.
#'
#' @return `NULL`, invisibly.
#'
finalize = function() {
lapply(private$observers, function(x) x$destroy())

if (!is.null(private$destroy_shiny)) {
private$destroy_shiny()
}
.finalize_session_bindings(self, private)
invisible(NULL)
},

Expand All @@ -173,22 +169,21 @@ FilterStateExpr <- R6::R6Class( # nolint
function(input, output, session) {
private$server_summary("summary")

private$observers[[session$ns("remove")]] <- observeEvent(
private$session_bindings[[session$ns("remove")]] <- observeEvent(
once = TRUE, # remove button can be called once, should be destroyed afterwards
ignoreInit = TRUE, # ignoreInit: should not matter because we destroy the previous input set of the UI
eventExpr = input$remove, # when remove button is clicked in the FilterState ui
handlerExpr = remove_callback()
)

private$destroy_shiny <- function() {
logger::log_debug("Destroying FilterStateExpr inputs; id: { private$get_id() }")

if (session$isEnded()) {
return(NULL)
} # skip input removal if session has ended
# remove values from the input list
lapply(session$ns(names(input)), .subset2(input, "impl")$.values$remove)
}
private$session_bindings[[session$ns("inputs")]] <- list(
destroy = function() {
logger::log_debug("Destroying FilterState inputs and observers; id: { private$get_id() }")
if (!session$isEnded()) {
lapply(session$ns(names(input)), .subset2(input, "impl")$.values$remove)
}
}
)

NULL
}
Expand Down Expand Up @@ -245,9 +240,8 @@ FilterStateExpr <- R6::R6Class( # nolint
# private members ----

private = list(
observers = NULL, # stores observers
session_bindings = list(), # stores observers and inputs to destroy afterwards
teal_slice = NULL, # stores reactiveValues
destroy_shiny = NULL, # function is set in server

# @description
# Get id of the teal_slice.
Expand Down
4 changes: 2 additions & 2 deletions R/FilterStateLogical.R
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ LogicalFilterState <- R6::R6Class( # nolint
NULL
})

private$observers[[session$ns("selected_api")]] <- observeEvent(
private$session_bindings[[session$ns("selected_api")]] <- observeEvent(
ignoreNULL = !private$is_multiple(),
ignoreInit = TRUE,
eventExpr = private$get_selected(),
Expand All @@ -335,7 +335,7 @@ LogicalFilterState <- R6::R6Class( # nolint
}
)

private$observers[[session$ns("selection")]] <- observeEvent(
private$session_bindings[[session$ns("selection")]] <- observeEvent(
ignoreNULL = FALSE,
ignoreInit = TRUE,
eventExpr = input$selection,
Expand Down
10 changes: 5 additions & 5 deletions R/FilterStateRange.R
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ RangeFilterState <- R6::R6Class( # nolint
})

# Dragging shapes (lines) on plot updates selection.
private$observers[[session$ns("relayout")]] <- observeEvent(
private$session_bindings[[session$ns("relayout")]] <- observeEvent(
ignoreNULL = FALSE,
ignoreInit = TRUE,
eventExpr = relayout_data(),
Expand Down Expand Up @@ -540,7 +540,7 @@ RangeFilterState <- R6::R6Class( # nolint
)

# Change in selection updates shapes (lines) on plot and numeric input.
private$observers[[session$ns("selection_api")]] <- observeEvent(
private$session_bindings[[session$ns("selection_api")]] <- observeEvent(
ignoreNULL = FALSE,
ignoreInit = TRUE,
eventExpr = private$get_selected(),
Expand All @@ -557,7 +557,7 @@ RangeFilterState <- R6::R6Class( # nolint
)

# Manual input updates selection.
private$observers[[session$ns("selection_manual")]] <- observeEvent(
private$session_bindings[[session$ns("selection_manual")]] <- observeEvent(
ignoreNULL = FALSE,
ignoreInit = TRUE,
eventExpr = selection_manual(),
Expand Down Expand Up @@ -712,7 +712,7 @@ RangeFilterState <- R6::R6Class( # nolint
# this observer is needed in the situation when private$teal_slice$keep_inf has been
# changed directly by the api - then it's needed to rerender UI element
# to show relevant values
private$observers[[session$ns("keep_inf_api")]] <- observeEvent(
private$session_bindings[[session$ns("keep_inf_api")]] <- observeEvent(
ignoreNULL = TRUE, # its not possible for range that NULL is selected
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
eventExpr = private$get_keep_inf(),
Expand All @@ -727,7 +727,7 @@ RangeFilterState <- R6::R6Class( # nolint
}
)

private$observers[[session$ns("keep_inf")]] <- observeEvent(
private$session_bindings[[session$ns("keep_inf")]] <- observeEvent(
ignoreNULL = TRUE, # it's not possible for range that NULL is selected
ignoreInit = TRUE, # ignoreInit: should not matter because we set the UI with the desired initial state
eventExpr = input$value,
Expand Down
Loading

0 comments on commit df87600

Please sign in to comment.