LocalStorage Usage Guide

A conversation with ChatGPT 4o about storing data in the browser using JavaScript with Shiny. See the original conversation on OpenAI.

Prompt

I’m in a workshop at posit::conf(2024) learning about using JavaScript with Shiny for R. Can you show me how to write to and read from local storage in the browser using JavaScript? Show me just the JavaScript snippets I need to know with a little bit of explanation. Don’t show me a full HTML example, though, because I’ll be using this in a Shiny app.

ChatGPT

Sure! Here are the JavaScript snippets you need to work with local storage in a browser:

Writing to Local Storage

To save data to local storage, you use the localStorage.setItem method. Here’s a simple example:

// Save a string value localStorage.setItem('key', 'value');
// Save a JavaScript object (you need to stringify it first)
const myObject = { name: 'John', age: 30 };
localStorage.setItem('myObject', JSON.stringify(myObject));

Reading from Local Storage

To retrieve data from local storage, you use the localStorage.getItem method. Here’s how you do it:

// Retrieve a string value
const value = localStorage.getItem('key');
console.log(value);  // Outputs: 'value'

// Retrieve a JavaScript object (you need to parse it back to an object)
const retrievedObject = JSON.parse(localStorage.getItem('myObject'));
console.log(retrievedObject);  // Outputs: { name: 'John', age: 30 }

Removing an Item from Local Storage

If you need to remove an item from local storage, you can use the localStorage.removeItem method:

localStorage.removeItem('key');

Clearing All Local Storage

To clear all data from local storage, you can use the localStorage.clear method:

localStorage.clear();

These snippets cover the basic operations for working with local storage. You can integrate them into your Shiny app’s JavaScript code as needed.

Prompt

I’m writing a Shiny for R app and want to store a variable in local storage so that the next time the user starts the app, I can retrieve the stored value, send it to the server, and initialize an input with the stored value. Show me how to do this, including a simple example. Use bslib for the UI. Don’t explain everything in the example app, just explain the JavaScript portion

ChatGPT

Sure! Here’s how you can achieve this in a Shiny app with bslib for the UI. The key parts are the JavaScript snippets for storing and retrieving the value from local storage and sending it to the server.

Shiny App Example

library(shiny)
library(bslib)

ui <- fluidPage(
  theme = bs_theme(bootswatch = "flatly"),
  tags$head(
    tags$script(HTML(
      "
      // Store a variable in local storage when the user inputs a value
      Shiny.addCustomMessageHandler('storeValue', function(message) {
        localStorage.setItem('storedValue', message.value);
      });

      // Retrieve the stored value from local storage when the app initializes
      document.addEventListener('DOMContentLoaded', function() {
        var storedValue = localStorage.getItem('storedValue');
        if (storedValue !== null) {
          Shiny.setInputValue('storedValue', storedValue);
        }
      });
      "
    ))
  ),
  titlePanel("Shiny Local Storage Example"),
  sidebarLayout(
    sidebarPanel(
      textInput("inputText", "Enter some text:", value = ""),
      actionButton("saveBtn", "Save to Local Storage")
    ),
    mainPanel(
      textOutput("outputText")
    )
  )
)

server <- function(input, output, session) {
  observeEvent(input$saveBtn, {
    session$sendCustomMessage("storeValue", list(value = input$inputText))
  })
  
  observeEvent(input$storedValue, {
    updateTextInput(session, "inputText", value = input$storedValue)
  })
  
  output$outputText <- renderText({
    paste("Stored value is:", input$inputText)
  })
}

shinyApp(ui = ui, server = server)

JavaScript Explanation

  1. Storing the Value:

    • The JavaScript code listens for a custom message (storeValue) from the Shiny server.
    • When the message is received, it stores the value in local storage using localStorage.setItem.
  2. Retrieving the Value:

    • When the document is loaded (DOMContentLoaded event), the JavaScript code checks if there’s a stored value in local storage.
    • If a value exists, it sets this value to a Shiny input (storedValue) using Shiny.setInputValue.

By incorporating these JavaScript snippets into your Shiny app, you can store and retrieve values from local storage, allowing for persistent input across sessions.

Prompt

In the release notes for Shiny 1.9.0, I read the following:

the Shiny object has a new property initializedPromise, which is a Promise-like object that can be awaited or chained with .then(). This Promise-like object corresponds to the shiny:sessioninitialized JavaScript event, but is easier to use because it can be used both before and after the events have occurred.

Can you update your example to take advantage of this new property?

ChatGPT

Certainly! Here’s the updated example that uses the Shiny.initializedPromise to handle the initialization of the session. This makes sure that the local storage value is set after the Shiny session has been properly initialized.

Updated Shiny App Example

library(shiny)
library(bslib)

ui <- fluidPage(
  theme = bs_theme(bootswatch = "flatly"),
  tags$head(
    tags$script(HTML(
      "
      // Store a variable in local storage when the user inputs a value
      Shiny.addCustomMessageHandler('storeValue', function(message) {
        localStorage.setItem('storedValue', message.value);
      });

      // Retrieve the stored value from local storage using initializedPromise
      Shiny.initializedPromise.then(function() {
        var storedValue = localStorage.getItem('storedValue');
        if (storedValue !== null) {
          Shiny.setInputValue('storedValue', storedValue);
        }
      });
      "
    ))
  ),
  titlePanel("Shiny Local Storage Example"),
  sidebarLayout(
    sidebarPanel(
      textInput("inputText", "Enter some text:", value = ""),
      actionButton("saveBtn", "Save to Local Storage")
    ),
    mainPanel(
      textOutput("outputText")
    )
  )
)

server <- function(input, output, session) {
  observeEvent(input$saveBtn, {
    session$sendCustomMessage("storeValue", list(value = input$inputText))
  })
  
  observeEvent(input$storedValue, {
    updateTextInput(session, "inputText", value = input$storedValue)
  })
  
  output$outputText <- renderText({
    paste("Stored value is:", input$inputText)
  })
}

shinyApp(ui = ui, server = server)

JavaScript Explanation with Shiny.initializedPromise

  1. Storing the Value:

    • The JavaScript code listens for a custom message (storeValue) from the Shiny server.
    • When the message is received, it stores the value in local storage using localStorage.setItem.
  2. Retrieving the Value:

    • Instead of directly listening to the DOMContentLoaded event, the code now uses Shiny.initializedPromise.
    • Shiny.initializedPromise is a Promise-like object that can be awaited or chained with .then().
    • When the Shiny session is initialized, the then function is executed, retrieving the stored value from local storage and setting it to a Shiny input (storedValue) using Shiny.setInputValue.

This updated approach ensures that the value from local storage is set only after the Shiny session has been fully initialized, improving the reliability of your app.

Prompt

Thanks for your help!

ChatGPT

You’re welcome! If you have any more questions or need further assistance, feel free to ask. Enjoy the rest of your workshop at posit::conf(2024)!