Easily hackable quick and dirty ad-hoc custom React components

So there’s a big process for publishing official dash components that’s documented and successfully used by multiple projects. And I’m sure it’s great, but to me it just looks like

Indeed, there are lots of posts here with folks trying to figure out how to wrap up and use other custom react components (that aren’t already bundled as dash components) and not really having success. I’m definitely not a React dev, nor am I a web dev; heck I wouldn’t even call myself a JS or Python programmer these days… although I can read and write hack on both fairly successfully. There are a few things that get in the way of me grabbing some react snippet off the web and using it in my Dash app:

  • React is huge
  • React typically depends a transpilation process (Babel) to convert ES6+JSX to bog-standard usable JS
  • Dash adds a tiny hook into React, but it’s not obvious what the secret sauce is
  • Dash autogenerates the structures it needs in Python/R/Julia to tell it how to connect to the JS and expose the docs.

Here’s the thing: there’s no magic. It’s just JSON + a few JS objects and/or functions and callbacks along with a very simple structure in the non-JS language to tell it what’s what. That’s it. We just need to structure a small bit of javascript in a way that dash expects. And that Javascript needn’t be buried away in some package or something — it can come right from the dash app itself. And the non-JS stuff needn’t be autogenerated; it can just be defined to mirror things in a little non-DRY manner.

Writing a React component without ES6/JSX

So you want to throw a react component into dash? The first step is removing the JSX + EC6 features 'cause we’re not gonna bother with a transpilation process. The guides from React for ES6 and JSX are fairly good, but I found the createReactClass to be quirkier/tougher than just defining the class directly; in practice there are just a few things to watch out for:

  • I trashed all imports/usings/requires/exports/etc. This’ll just run as plain old JS. If you want to use some npm dependency you can add it from unpkg.com and throw that URL as one of your external scripts in the dash app. You can fully qualify React.* things — I think it’s fairly safe that React will mean what you expect when dash is loaded.
  • If you’re using a class-based class ____ extends React.Component component, remember that member functions don’t auto-bind this when called as higher-order functions. A simple workaround is to bind this for every member function you have within your constructor with the crufty old versions of JS available in our browsers. So if I have an update function in my class, I just need to ensure I have a this.update.bind(this); line in my constructor.
  • Convert the JSX to the annoying and explicit React.createElement(...) function calls.

Making that React component talk to Dash

  • I think it’s Dash itself that adds a setProps function to the props of your component that makes state updates visible to dash. In any case, there’s typically not setProps calls the React component examples out there… use it to update the state that you want to have visible to dash. The typical idiom goes something like:

    if (props.setProps) { // it's this.props.setProps if class-based
        props.setProps({value: newValue});
    }
    
  • Dash really wants your component to be namespaced, so you gotta stash it into a JS dict/object/json-blob-thing/whatever-it’s-called since JS doesn’t really have namespaces. As simple as:

    var custom_components = {
      CustomClassComponent: class CustomClassComponent extends React.Component { ... }
      # or
      CanvasFunctionalComponent: function(props) { ... }
    }
    

    Now it’ll be accessible as dash expects: custom_components.CustomWhateverComponent.

  • Add the propTypes for the props you want to expose to dash. It needn’t be comprehensive, and while these are typically nicely commented (I think that’s part of the black-magic auto-documenting auto-generating toolchain), that’s not really necessary for our quick-and-dirty purposes.

    custom_components.CustomWhateverComponent.propTypes = {
      id: PropTypes.string,
      ...
    }
    

Making the non-JS Dash talk to that React component

On the non-JS-language side, we simply need to tell dash a handful of things. It’s really just the namespace, the component name, and which properties are available for dash to fiddle with. This part will vary based upon your language, and I really one care about one language here… perhaps others could fill in this part for R and Python:

  • Julia: We can simply call the internal Dash.Component constructor appropriately — and can even wrap it up into a function to behave like a typical published function

    custom_whatever_component(;id, kwargs...) = Dash.Component(
        "CustomWhateverComponent",  # The name 
        "CustomWhateverComponent",  # Also the name? Well, one name is for JS, one is for Julia, but the Julia one doesn't matter, and I didn't care to figure it out
        "custom_components",        # The namespace
        Symbol[:id, #= others… =#], # All the props that I want to expose (and listed in propTypes)
        Symbol.(["data-","aria-"]), # This is what the official components seem to use. Monkey see monkey do.
        id=id;                      # Require an id
        kwargs...)                  # Pass along all other kwargs — these will need to match the supported props above
    
  • Python/R: ¯\_(ツ)_/¯ I’m sure it’s something akin to the above. Perhaps this constructor could be standardized across languages and documented in the future?

The final example:

ezgif.com-gif-maker-2

This was all done to clean up and improve the demo I used in our joint Plotly + Julia Computing official announcement of Dash support for Julia. For this demo, I needed a canvas input… and of course the html_canvas component from DashHTMLComponents isn’t very helpful without JS callbacks. For the webinar, I had hand-jammed the JS onto the html_canvas component through a loader callback… but that performed terribly, looked crufty, and I knew there had to be a better way. This is the better way. Repo here, and I should have this into a heroku/JuliaHub-deployable state in the near future. Tucked away in the history is an initial class-based attempt, and the final version is function-based (since that’s what’s required for getting a canvas to talk to React).

Hopefully this post will make it easier the next time someone wants to accomplish something like this!

7 Likes