Authoring inputs powered by React with reactR

Alan Dipert and Carson Sievert

2023-10-10

Shiny comes with a large library of input widgets for collecting input from the user and conveying input data to R.

If you want a kind of input not provided by Shiny — like a color picker, or a different kind of slider — you’ve always been able to build your own. Shiny’s input system is extensible. All that’s required is an understanding of certain conventions and a little custom JavaScript.

reactR provides additional tools to ease the creation of new Shiny inputs implemented using React. In the following tutorial, we will demonstrate these tools by implementing a new Shiny color picker input that wraps the react-color library.

Software pre-requisites

In order to develop a reactR Shiny input, you’ll need to install R and optionally RStudio. If you’re on Windows, you should also install Rtools.

For an excellent general introduction to R package concepts, check out the R packages online book.

In addition, you’ll need to install the following JavaScript tools on your machine:

To follow along in this vignette, you’ll also need the following R packages:

install.packages(c("shiny", "devtools", "usethis", "reactR"))

Scaffolding

To create a new widget you can call scaffoldReactShinyInput to generate the basic structure and build configuration. This function will:

The following R code will create an R package named colorpicker, then provide the templating for creating an input powered by the react-color library on npm:

# Create the R package (rstudio=TRUE is recommended if you're not already comfortable with your terminal)
usethis::create_package("~/colorpicker", rstudio = TRUE)
# Scaffold initial input implementation files
withr::with_dir(
  "~/colorpicker",
  reactR::scaffoldReactShinyInput("colorpicker", list("react-color" = "^2.17.0"), edit = FALSE)
)

Building and installing

Building the JavaScript

The next step is to navigate to the newly-created colorpicker project and run the following commands in the terminal. If you’re new the terminal, we recommend opening your newly created RStudio ~/colorpicker/colorpicker.Rproj project file, then running the following in the RStudio terminal tab:

yarn install
yarn run webpack

yarn run webpack is not strictly a yarn command. In fact, yarn run simply delegates to the webpack program. Webpack’s configuration is generated by scaffoldReactShinyInput in the file webpack.config.js, but you can always change this configuration and/or modify the yarn run webpack command to suit your needs.

Installing the R package

Now that the input’s JavaScript is compiled, go ahead and install the R package:

devtools::document()
devtools::install(quick = TRUE)

In RStudio, you can use the keyboard shortcuts Ctrl-Shift-D and Ctrl-Shift-B to document and build the package. (On macOS, the shortcuts are Cmd-Shift-D and Cmd-Shift-B)

Run the included demo

Now that the input’s JavaScript is compiled, and the R package is installed, run app.R to see a demo in action:

shiny::runApp()

In RStudio, you can open app.R and press Ctrl-Shift-Enter (Cmd-Shift-Enter on macOS). You should see something like the following appear in the Viewer pane:

Authoring a React input

At this point, we have a working (if simple) React-powered text input. Let’s modify it to create an interface to the react-color library.

Connecting Shiny with React

Consider the following example taken from the react-color documentation.

import React from 'react';
import { SketchPicker } from 'react-color';

class Component extends React.Component {

  render() {
    return <SketchPicker />;
  }
}

That JavaScript code produces a SketchPicker-type interface that looks like this:

However, that example doesn’t demonstrate a way to default to a particular color, or a way to cause something to happen when the color changes. To accomplish these, react-color components can optionally take the following props:

Since this React component calls a configurable function (i.e., onChangeComplete) when the input (i.e., color) value changes, we can supply a function to inform Shiny about these changes. You could, in theory, do this by writing your own custom Shiny input wrapper around this component, but reactR provides some conventions to make it much easier. These conventions have two main parts (R and JavaScript):

  1. Use reactR::createReactShinyInput() to construct the user-facing R input and route any user-supplied options (e.g., the default input value and other configuration) to the React component. This part was already done for us in the R/colorpicker.R file of our colorpicker project:
colorpickerInput <- function(inputId, default = "") {
  reactR::createReactShinyInput(
    inputId = inputId,
    class = "colorpicker",
    dependencies = htmltools::htmlDependency(
      name = "colorpicker-input",
      version = "1.0.0",
      src = "www/colorpicker/colorpicker",
      package = "colorpicker",
      script = "colorpicker.js"
    ),
    default = default,
    configuration = list(),
    container = htmltools::tags$span
  )
}
  1. Design an intermediate React component that routes information from colorpickerInput() to the <SketchPicker> component and also inform Shiny when a new color is chosen. This intermediate component should be a functional component with three arguments:

Consider the following intermediate component, PickerInput. Note how this intermediate component allows one to set the default value from R and also calls setValue() inside onChangeComplete in order to inform Shiny about new color values. Finally, reactR.reactShinyInput() registers this intermediate component as a custom Shiny input binding named colorpicker.

import { reactShinyInput } from 'reactR';
import { SketchPicker } from 'react-color';

const PickerInput = ({ configuration, value, setValue }) => {
  return (
    <SketchPicker
      color={ value }
      onChangeComplete={ color => setValue(color.hex) }
    />
  );
};

// Note the first argument here should match the `class` 
// argument of the reactR::createReactShinyInput() from step 1
reactShinyInput('.colorpicker', 'colorpicker', PickerInput);

Open the srcjs/colorpicker.jsx file in your colorpicker project and paste this code into it. After saving the file, run yarn run webpack in the terminal, re-install the package, then run shiny::runApp() again

When you select new colors, you should see the textOutput update accordingly.

You might have noticed that the input showed up initially without a color selected. That’s because in app.R we didn’t supply a default argument to the colorpickerInput function inside our ui.

Try replacing the call to colorpickerInput with this: colorpickerInput("textInput", default = "#a76161")

Now when you run the app, the color should start as a shade of red.

Further learning

This tutorial walked you through the steps taken to wrap the react-color library in a Shiny input. The full example package is accessible at https://github.com/react-R/colorpicker-example. Our intention is keep creating example packages under the https://github.com/react-R organization, so head there if you’d like to see other examples of interfacing with React.