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 thatReact
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-bindthis
when called as higher-order functions. A simple workaround is to bindthis
for every member function you have within your constructor with the crufty old versions of JS available in our browsers. So if I have anupdate
function in my class, I just need to ensure I have athis.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 notsetProps
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 functioncustom_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:
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!