Skip to main content

React Integration

The jsPlumb Toolkit has several components to assist you to integrate with React - this page provides an overview. The Toolkit requires React 17 or later.

Imports

The React integration ships in the package @jsplumbtoolkit/browser-ui-react.

TOP


Setup

The main component you will use is the JsPlumbToolkitSurfaceComponent.

Let's take a quick look at how you'd use one:

With Hooks


import React, { useEffect, useRef } from "react"

import {
JsPlumbToolkitMiniviewComponent,
JsPlumbToolkitSurfaceComponent,
BrowserUIReact
}
from '@jsplumbtoolkit/browser-ui-react';

import { newInstance } from "@jsplumbtoolkit/browser-ui"

export default function DemoComponent({someProps}) {

const surfaceComponent = useRef(null)

const toolkit = newInstance({
...
})

const view = {
...
}

const renderParams = {
...
}

useEffect(() => {
toolkit.load({url:"/copyright.json"})
}, [])


return <div style={{width:"100%",height:"100%"}}>
<JsPlumbToolkitSurfaceComponent renderParams={renderParams} toolkit={toolkit} view={view} ref={ surfaceComponent} }/>
</div>

}

First we create a ref to a surfaceComponent, and then create an instance of the Toolkit, and declare a view and renderParams. In the JSX we return we declare a JsPlumbToolkitSurfaceComponent and inject the render params, view and toolkit, and hook up the surface component ref.

With components


import { JsPlumbToolkitMiniviewComponent, JsPlumbToolkitSurfaceComponent, BrowserUIReact } from '@jsplumbtoolkit/browser-ui-react'

class DemoComponent extends React.Component {

constructor(props) {
super(props);
this.toolkit = jsPlumbToolkit.newInstance({
...
});

this.view = {
...
}

this.renderParams = {
...
}
}

render() {
return <div style={{width:"100%",height:"100%"}}>
<JsPlumbToolkitSurfaceComponent renderParams={this.renderParams} toolkit={this.toolkit} view={this.view} ref={ (c) => { if (c != null) this.surface = c.surface } }/>
<ControlsComponent ref={(c) => this.controls = c }/>
<div className="miniview"/>
</div>
}

}

We create an instance of the Toolkit in the component's constructor, which we then inject into the JsPlumbToolkitSurfaceComponent, along with renderParams and the view.

ControlsComponent is something built for the Toolkit React demonstrations, to handle zoom/selection of nodes. The code for this component does not ship in the React integration package, but you are welcome to grab the code from one of the demonstrations if you wish to use it.


Components

React is component based. The Toolkit offers 3 components, which you can use in the same way regardless of whether your app uses Hooks or Components.

JsPlumbToolkitSurfaceComponent

<JsPlumbToolkitSurfaceComponent renderParams={this.renderParams} view={this.view} toolkit={this.toolkit}/>

Attributes

  • toolkit A reference to an instance of the Toolkit. Required.
  • renderParams Parameters to pass in to the constructor of the Surface widget. Optional, but you'll probably supply something here.
  • view View parameters. Views are discussed here. Again, optional, but you'll probably want to supply something.

TOP


JsPlumbToolkitMiniviewComponent

Provides a Miniview that can be attached to some Surface component.

Example


imnport React from "react"
import { createRoot } from 'react-dom/client';

import { JsPlumbToolkitMiniviewComponent } from '@jsplumbtoolkit/browser-ui-react';

const miniview = createRoot(someDOMElement)
miniview.render(
<JsPlumbToolkitMiniviewComponent surface={a surface component}/>)
)

Attributes

  • surface The JsPlumbToolkitSurfaceComponent to which to attach the Miniview component.

TOP


SurfaceDropComponent

Provides a means to implement drag/drop of new Nodes/Groups onto your Surface. This component is abstract- you are expected to provide the render method.

Example

First, declare your subclass and provide the render method:


import { SurfaceDropComponent } from '@jsplumbtoolkit/browser-ui-react';

class MyPalette extends SurfaceDropComponent {

render() {
return <div className="someClass">
<ul>
<li data-node-type="foo" jtk-is-group="true">FOO</li>
<li data-node-type="bar">BAR</li>
</ul>
</div>
}
}

Then create one in your app:


imnport React from "react"
import { createRoot } from 'react-dom/client';

const typeExtractor = function(el) { return el.getAttribute("data-node-type") };
const dataGenerator = function (type) { return { w:120, h:80 }; };
const nodePaletteElement = document.querySelector(".parentOfNodePalette")

const nodePalette = createRoot(nodePaletteElement)
nodePalette.render(
<MyPalette surface={this.surface} selector={"li"} container={nodePaletteElement} dataGenerator={dataGenerator}/>);

As with the Miniview component, this component needs a reference to a JsPlumbToolkitSurfaceComponent.

Attributes

  • selector:string A valid CSS3 selector identifying descendant nodes that are to be configured as draggable/droppables.
  • dataGenerator:(el:HTMLElement) => T This function is used to provide default data for some node/group. Note that a difference between this component and the original jsplumb-palette is that your dataGenerator function is now expected to determine the "type" of the object being dragged, and to set it on the data object if desired.
  • surface Required. The JsPlumbToolkitSurfaceComponent to which to attach the Drop Manager.
  • allowDropOnGroup:boolean Optional, defaults to true. If true, then elements can be dropped onto nodes/groups, and in the event that occurs, the onDrop method will be called.
  • allowDropOnCanvas:boolean Optional, defaults to true. When an element is dropped on the canvas whitespace, it is added to the dataset and rendered.
  • allowDropOnEdge:boolean Optional, defaults to true. If true, then elements can be dropped onto edges, and in the event that an element is dropped on an edge, a new node/group is added and inserted between the source and target of the original edge, and the original edge is discarded..
  • typeGenerator:(data:T) => string Optional. A function that can return the correct type for some data object representing an element being dragged. By default the Toolkit will use the type member of the data object.
  • groupIdentifier:(d: T, el: HTMLElement) => boolean Optional. By default, the toolkit looks for a jtk-is-group attribute on an element being dragged. If found, with a value of "true", then the Toolkit assumes a group is being dragged. You can supply your own function to make this decision.

For further reading, see this page .

TOP


Rendering Nodes and Groups

Each Node or Group in your UI is rendered as an individual component. As an example, consider the component we use to render an Action node in the Flowchart Builder:

import * as React from 'react';

import { ShapeComponent } from "@jsplumbtoolkit/browser-ui-react"

export default function NodeComponent({ctx, shapeLibrary}) {

const { vertex } = ctx;
const data = vertex.data;

return <div style={{width:data.width + 'px',height:data.height + 'px',color:data.textColor}} className="flowchart-object" data-jtk-target="true" data-jtk-target-port-type="target">
<span>{data.text}</span>
<ShapeComponent obj={data} shapeLibrary={shapeLibrary}/>
<div className="jtk-connect jtk-connect-left" data-jtk-anchor-x="0" data-jtk-anchor-y="0.5" data-jtk-orientation-x="-1" data-jtk-orientation-y="0" data-jtk-source="true" data-jtk-port-type="source"></div>
<div className="node-delete node-action delete"></div>
</div>
}

Mapping to a type

Amongst other things, the view is used to map node/group types to their rendering. There are two ways to map a node/group type to what gets rendered - mapping a component, or providing some JSX.

Mapping components

This is the nodes section from the view in the Flowchart Builder demonstration:

this.view = {
nodes: {
"start": {
component:StartComponent
},
"selectable": {
events: {
tap: (params) => {
this.toolkit.toggleSelection(params.node);
}
}
},
"question": {
parent: "selectable",
component:QuestionComponent
},
"action": {
parent: "selectable",
component:ActionComponent
},
"output":{
parent:"selectable",
component:OutputComponent
}
},
// There are two edge types defined - 'yes' and 'no', sharing a common
// parent.
edges: {
...
}
}

Here, we map node types to various classes. In previous versions of the Toolkit, it was imperative that you extended BaseNodeComponent or BaseGroupComponent in the components you used to render nodes/groups. This is no longer the case, but the base components do have a few useful methods. In the Flowchart Builder demonstration each of the components shown, as discussed above, extend BaseEditableComponent, which itself extends the Toolkit's BaseNodeComponent, and that component extends React.Component.

Using JSX

An alternative to mapping component classes is to provide some JSX. The view shown above actually now looks like this the demonstration code we ship:

this.view = {
nodes: {
"start": {
jsx:(ctx) => { return <StartComponent ctx={ctx} dlg={dialogManager}/> }
},
"selectable": {
events: {
tap: (params) => {
this.toolkit.toggleSelection(params.node);
}
}
},
"question": {
parent: "selectable",
jsx:(ctx) => { return <QuestionComponent ctx={ctx} dlg={dialogManager}/> }
},
"action": {
parent: "selectable",
jsx:(ctx) => { return <ActionComponent ctx={ctx} dlg={dialogManager}/> }
},
"output":{
parent:"selectable",
jsx:(ctx) => { return <OutputComponent ctx={ctx} dlg={dialogManager}/> }
}
},
// There are two edge types defined - 'yes' and 'no', sharing a common
// parent.
edges: {
...
}
}

When you use the jsx approach, you're expected to provide a function that takes ctx as argument, and returns JSX. If, as in this demonstration, your classes extend BaseNodeComponent (or BaseGroupComponent) you should pass ctx in as a prop, so that the base class can extract the things it needs to provide the various support methods it offers. If your component does not extend one of the Toolkit's base components, though, then obviously passing ctx in as a prop is entirely up to you.

ctx is an object containing five things:

interface JsxContext {
vertex: Node | Group; // the vertex - node or group - that is being rendered by this JSX
surface: Surface; // the Surface widget that is rendering the vertex
toolkit: JsPlumbToolkit; // the underlying JsPlumbToolkit instance
data: Record<string, any>; // the vertex's backing data. This is just a convenience - it can be accessed via `vertex.data`
props: Record<string, any>; // these are any props that you passed in as the `childProps` argument to a `JsPlumbToolkitSurfaceComponent`. See below.
}

As mentioned above, you can pass back arbitrary JSX from these functions.

In this view you'll also see we pass a dialogManager in to each component - the demonstration uses this as a common manager for operations that require prompting the user with a dialog.

Passing props

With the JSX approach you can pass props in to the components used to render your vertices by setting them on the childProps prop of a Surface component. For instance, in the React Hooks Skeleton demonstration, DemoComponent - the main app - declares a random color variable in its initial state:

class DemoComponent extends React.Component {

constructor(props) {
super(props);
this.toolkit = jsPlumbToolkit.newInstance();
this.state = { color:randomColor() };

}

}

In the render method of the DemoComponent, a surface component is created, and the color member of the main component's state is passed in:

render() {
return <div style={{width:"100%",height:"100%",display:"flex"}}>
<button onClick={this.changeColor.bind(this)} style={{backgroundColor:this.state.color}} className="colorButton">Change color</button>
<JsPlumbToolkitSurfaceComponent childProps={{color:this.state.color}} renderParams={this.renderParams} toolkit={this.toolkit} view={this.view} />
</div>
}

In the view for the DemoComponent, each of the node types references the Surface's childProps by way of the context they are given:

this.view = {
nodes: {
"shin":{
jsx: (ctx) => { return <ShinBoneComponent color={ctx.props.color} ctx={ctx}/> }
},
"knee":{
jsx: (ctx) => { return <KneeBoneComponent color={ctx.props.color} ctx={ctx}/> }
}
},
...
}

So, here, ctx.props.color is referencing this.state.color in DemoComponent. If we change DemoComponent's state, the change propagates through to the vertex components:

changeColor() {
const current = this.state.color;
let col;
while (true) {
col = randomColor();
if (col !== current) {
break;
}
}
this.setState({ color:col } )
}

BaseNodeComponent

As a convenience, your components can extend BaseNodeComponent. If you use the jsx approach described above, you should pass ctx in as a prop; if you use a class reference via component, the Toolkit will take care of passing everything the base component needs.

BaseNodeComponent offers a couple of useful methods:

  • removeNode() - instructs the underlying Toolkit to remove the Node this component represents. The node will be removed from the model, and the component will be unmounted.
  • updateNode(data:Record<string, any>) - updates the underlying Node's data model. This writes to the backing data for the node, which React is managing, and so the JSX will respond accordingly. The Toolkit will respond to any changes in the UI by ensuring all edges are still connected in the appropriate locations.

as well as these properties:

  • node - The Node object the component is rendering
  • surface - The Surface by which the Node was rendered
  • toolkit - The underlying JsPlumbToolkit instance.

BaseGroupComponent

This component is conceptually identical to BaseNodeComponent, but with different method names/properties:

  • removeGroup() vs removeNode()
  • updateGroup(...) vs updateNode(...)
  • group vs node

BasePortComponent

If your UI makes use of ports, like the Schema Builder demonstration, you can extend BasePortComponent in your port components. Here's how the table node type from the referenced demonstration writes out elements representing its columns:

<div className="table-columns">
{ this.node.data.columns.map(c => <ColumnComponent data={c} key={c.id} parent={this}/>) }
</div>

Each of the props shown here is required.

  • data - This is the backing data for the Port
  • key - Provide a unique key for each Port, to ensure that React only renders it once. If the Port gets re-rendered the underlying Toolkit renderer will lose the DOM element it is tracking.
  • parent - A reference to the component rendering the Vertex (Node/Group) to which the Port belongs. The BasePortComponent needs this in order to perform some essential housekeeping.

BasePortComponent offers a few helper methods:

  • getPort():Port
  • getPortId():string
  • removePort():boolean
  • updatePort(data:Record<string, any>):void

TOP


Shape libraries

A shape library is a set of named shapes that you will use to render SVG inside the vertices in your application. These are discussed in detail in the shape libraries and shape-sets pages; here we provide an overview of the available React integration with these modules.

Creating a shape library

In React, a shape library is created the same way as in Vanilla js:

import { FLOWCHART_SHAPES, ShapeLibraryImpl } from "@jsplumbtoolkit/browser-ui"

const shapeLibrary = new ShapeLibraryImpl(FLOWCHART_SHAPES)

Rendering an SVG shape

The ShapeLibraryComponent offers a means to render SVG from a shape library into your components. It is shipped in the Toolkit's Angular module, so you do not need to do anything extra to import it. To use this component, you simply embed it in your vertex component's template. This is the template for a node in the flowchart demonstration app:

<div style="width:{{obj.width}}px;height:{{obj.height}}px;color:{{obj.textColor}}" class="flowchart-object" data-jtk-target="true" data-jtk-target-port-type="target">
<span>{{obj.text}}</span>

<jtk-shape [obj]="obj" [width]="obj.width" [height]="obj.height"></jtk-shape>

<div *ngFor="let anchor of anchorPositions"
class="jtk-connect jtk-connect-{{anchor.id}}"
[attr.data-jtk-anchor-x]="anchor.x"
[attr.data-jtk-anchor-y]="anchor.y"
[attr.data-jtk-orientation-x]="anchor.ox"
[attr.data-jtk-orientation-y]="anchor.oy"
data-jtk-source="true"></div>

<div class="node-delete node-action delete" (click)="this.removeNode()"></div>
</div>
note

It is necessary to supply obj and also [width]="obj.width" and [height]="obj.height" to the component; width and height may seem redundant here as they come from obj, but Angular's change detection will not automatically pick up changes to obj.width and obj.height if you only pass obj in to the shape component.

tip

Note also that in this template we have also written width and height into the root element's style. We do this because the template contains more than just the SVG shape, and we want to ensure that the size is honoured. If your template contained only the SVG shape and nothing else then you would not need to set width and height in the root element's style.

Dragging new SVG shapes

The Angular integration contains a component renders the contents of some shape library into a palette from which you can drag new shapes onto the canvas. In the vanilla shape library this component is instantiated directly, but in Angular you use the ShapeLibraryPaletteComponent:

<div class="my-component">

<jsplumb-surface toolkitId="toolkit" surfaceId="surface" ... />

<jtk-shape-palette surfaceId="surface" shapeLibraryId="my-shape-library"></jtk-shape-palette>

</div>

In this most basic setup we've created a surface component with the surface id surface, and then we've created a shape library palette and provided it with the same surface id, as well as a shapeLibraryId. The shape library palette will retrieve the surface and the shape library from the jsPlumb service, and then render the shape library, configuring a drag/drop manager to allow shapes to be dragged on to the surface's canvas.

Attributes

  • surfaceId:string Required. The ID of the surface to which to attach the palette.
  • shapeLibraryId:string The ID of some shape library you have registered on the jsPlumb service. This attribute is in fact optional, as if you omit the shape library ID the Toolkit will use a default value. For applications in which you have only one shape library this is fine - but remember not to provide an ID when you register the shape library!
  • dragSize:Size Optional size to set on elements when they are being dragged. If this is omitted, the derived iconSize will be used.
  • iconSize:Size Optional, defaults to 150x100 pixels. This is the size to use for each icon. It will also be used for dragSize if that is not separately specified.
  • fill:string Optional, defaults to "#FFFFFF". Fill color to use for icons.
  • outline:string Optional, defaults to "#000000". Color to use for icon outline.
  • selectAfterDrop:boolean Optional, defaults to true. Instructs the palette to set a newly dropped vertex as the Toolkit's selection.
  • canvasStrokeWidth:number Optional, defaults to 2. The stroke width to use when rendering dropped vertices on the canvas.
  • paletteStrokeWidth:number Optional, defaults to 1. The stroke width to use when rendering icons in the palette.
  • dataGenerator:DataGeneratorFunction Optional. Provide one of these if you wish to be able to set initial data for some icon that is about to be dragged on to the canvas.