Shiny仪表盘中响应式对象更新:健身活动追踪状态持久化问题
健身活动追踪仪表盘状态持久化问题
问题描述
我正在开发一个健身活动追踪仪表盘,采用Google Sheets作为后端存储。应用启动时会加载今日、昨日、前日的所有活动数据;初始界面展示当日可用活动的开关组件,开关状态从Google Sheets数据源预加载。
界面设有时间框架切换控件,可在今日、昨日、前日之间切换。目前存在的问题是:切换日期后,之前对某一天活动开关的修改,在切回该日期时会丢失。
我需要一个响应式对象,初始值为从Google Sheets下载的原始状态,每次点击开关时能更新对应日期对应活动的状态;最终仅在点击“上传”按钮时将修改同步至Google Sheets(该功能已实现)。
当前代码
library(tidyverse) library(shiny) library(shinyWidgets) timeframes <- tribble( ~date_name, ~date, "Today", today() , "Yesterday", today()-1 , "Day before yesterday", today()-2 ) pre_assignment_vector <- tibble::tribble( ~feature, ~date_name, ~value, "bike_into_coworking_space", "Day before yesterday", FALSE, "bike_into_coworking_space", "Today", FALSE, "bike_into_coworking_space", "Yesterday", TRUE, "yoga", "Day before yesterday", FALSE, "yoga", "Today", FALSE, "yoga", "Yesterday", TRUE ) %>% mutate(value_name = paste0(feature, "_", snakecase::to_snake_case(date_name), "_status"), input_id_destination = paste0(feature, "_switch") ) assignment_vector <- pre_assignment_vector %>% filter(date_name == "Today") map2(assignment_vector$value_name, assignment_vector$value, ~assign(.x, .y, envir = .GlobalEnv)) ################################################################### reactive_store_of_values_obj <- reactive(pre_assignment_vector) ################################################################### # 日期切换时更新开关状态的函数 update_switches_to_date_selected <- function(reactive_store_of_values_obj, timeframe_selected) { values_to_update_to <- reactive_store_of_values_obj %>% filter(date_name == timeframe_selected) status_vals <- values_to_update_to map2(status_vals$input_id_destination, status_vals$value, ~updateSwitchInput(inputId = .x, value = as.logical(.y))) } ################################################################### # 开关变化时更新存储对象的函数 update_the_reactive_store_of_values_obj <- function(reactive_store_of_values_obj , timeframe_selected = input$timeframe_selected , yoga_switch = input$yoga_switch , bike_into_coworking_space_switch = input$bike_into_coworking_space_switch) { new_values_from_inputs <- tibble(yoga_switch = yoga_switch, bike_into_coworking_space_switch = bike_into_coworking_space_switch) %>% gather(input_id_destination, new_value) %>% mutate(date_name = timeframe_selected) reactive_store_of_values_obj %>% left_join(new_values_from_inputs, by = join_by(date_name, input_id_destination)) %>% mutate(value = coalesce(new_value, value)) %>% select(-new_value) } ################################################################### ui <- fluidPage( mainPanel( fluidRow( h3("Timeframe"), sliderTextInput(inputId = "timeframe_selected", label = NULL, choices = rev(timeframes$date_name), selected = timeframes$date_name[1]) ), fluidRow( column(width = 2, h4("Yoga")), column(width = 1, prettySwitch(inputId = "yoga_switch", label = NULL, value = as.logical(yoga_today_status))), ), fluidRow( column(width = 2, h4("Bike into Coworking Space")), column(width = 1, prettySwitch(inputId = "bike_into_coworking_space_switch", label = NULL, value = as.logical(bike_into_coworking_space_today_status))) ) ) ) server <- function(input, output) { observeEvent(ignoreInit = TRUE, input$timeframe_selected, { update_switches_to_date_selected(reactive_store_of_values_obj = reactive_store_of_values_obj(), timeframe_selected = input$timeframe_selected) }) ################################################################### all_inputs_watch_list <- reactive( list( input$yoga_switch , input$bike_into_coworking_space_switch )) observeEvent(ignoreInit = TRUE, all_inputs_watch_list(), { print("switch used") reactive_store_of_values_obj <- reactive(update_the_reactive_store_of_values_obj(reactive_store_of_values_obj())) }) } shinyApp(ui = ui, server = server)
解决方案
问题核心在于使用reactive()存储状态无法持久化修改——reactive()是被动响应式对象,每次重新赋值都会创建新的实例,旧状态会丢失。改用reactiveVal()可创建可主动更新的响应式存储,实现状态持久化。
修改后的完整代码
library(tidyverse) library(shiny) library(shinyWidgets) timeframes <- tribble( ~date_name, ~date, "Today", today() , "Yesterday", today()-1 , "Day before yesterday", today()-2 ) pre_assignment_vector <- tibble::tribble( ~feature, ~date_name, ~value, "bike_into_coworking_space", "Day before yesterday", FALSE, "bike_into_coworking_space", "Today", FALSE, "bike_into_coworking_space", "Yesterday", TRUE, "yoga", "Day before yesterday", FALSE, "yoga", "Today", FALSE, "yoga", "Yesterday", TRUE ) %>% mutate(value_name = paste0(feature, "_", snakecase::to_snake_case(date_name), "_status"), input_id_destination = paste0(feature, "_switch") ) assignment_vector <- pre_assignment_vector %>% filter(date_name == "Today") map2(assignment_vector$value_name, assignment_vector$value, ~assign(.x, .y, envir = .GlobalEnv)) ################################################################### # 改用reactiveVal创建可持久化的响应式存储 reactive_store_of_values_obj <- reactiveVal(pre_assignment_vector) ################################################################### # 更新开关状态:从reactiveVal读取最新数据 update_switches_to_date_selected <- function(timeframe_selected) { current_data <- reactive_store_of_values_obj() values_to_update_to <- current_data %>% filter(date_name == timeframe_selected) map2(values_to_update_to$input_id_destination, values_to_update_to$value, ~updateSwitchInput(inputId = .x, value = as.logical(.y))) } ################################################################### # 更新存储对象:直接修改reactiveVal的值 update_the_reactive_store_of_values_obj <- function(timeframe_selected, yoga_switch, bike_switch) { current_data <- reactive_store_of_values_obj() new_values_from_inputs <- tibble( input_id_destination = c("yoga_switch", "bike_into_coworking_space_switch"), new_value = c(yoga_switch, bike_switch) ) %>% mutate(date_name = timeframe_selected) updated_data <- current_data %>% left_join(new_values_from_inputs, by = join_by(date_name, input_id_destination)) %>% mutate(value = coalesce(new_value, value)) %>% select(-new_value) reactive_store_of_values_obj(updated_data) } ################################################################### ui <- fluidPage( mainPanel( fluidRow( h3("Timeframe"), sliderTextInput(inputId = "timeframe_selected", label = NULL, choices = rev(timeframes$date_name), selected = timeframes$date_name[1]) ), fluidRow( column(width = 2, h4("Yoga")), column(width = 1, prettySwitch(inputId = "yoga_switch", label = NULL, value = as.logical(yoga_today_status))), ), fluidRow( column(width = 2, h4("Bike into Coworking Space")), column(width = 1, prettySwitch(inputId = "bike_into_coworking_space_switch", label = NULL, value = as.logical(bike_into_coworking_space_today_status))) ), # 保留上传按钮位置(你已实现上传逻辑) fluidRow( column(width = 3, actionButton("upload_btn", "上传修改到Google Sheets")) ) ) ) server <- function(input, output) { # 切换日期时更新开关状态 observeEvent(ignoreInit = TRUE, input$timeframe_selected, { update_switches_to_date_selected(timeframe_selected = input$timeframe_selected) }) # 监听开关变化,实时更新存储对象 observeEvent(c(input$yoga_switch, input$bike_into_coworking_space_switch), { req(input$timeframe_selected) update_the_reactive_store_of_values_obj( timeframe_selected = input$timeframe_selected, yoga_switch = input$yoga_switch, bike_switch = input$bike_into_coworking_space_switch ) }) # 上传按钮逻辑(你已实现,示例留空) observeEvent(input$upload_btn, { # 从reactive_store_of_values_obj()获取最新数据,同步到Google Sheets showNotification("修改已上传", type = "success") }) } shinyApp(ui = ui, server = server)
关键修改说明
- 状态存储:用
reactiveVal()替代reactive(),允许主动调用reactive_store_of_values_obj(updated_data)更新存储内容,实现状态持久化。 - 函数逻辑调整:移除对全局
input的依赖,通过参数传递输入值,避免作用域问题;更新函数直接修改reactiveVal的存储值。 - 监听简化:直接监听开关输入变化,无需额外创建监听列表,代码更简洁高效。
- 日期切换:从
reactiveVal读取最新数据,确保切换日期时加载的是修改后的状态。
内容的提问来源于stack exchange,提问作者Julian Tagell
相关产品推荐
相关产品推荐

