Has anyone attempted a dashflow component from Reactflow?

Hi all

Found this interesting react package and I can easily imagine as part of the Dash tools

https://reactflow.dev/

I have attempted to follow the “create your own components” wrapping up a react package and I got lost in the documentation,
Also noting some of the latest npm package not quite aligned with the list of the directions in setting up
There is also this latest reference that i tried to follow:

https://community.plotly.com/t/creating-custom-dash-components/72547/6

But the resulting template following this latest direction is nowhere near the initial template to follow the original documentation, reactflow has also quite a few features to bring and I got lost in several attempts to make it work.

Could this be something in line of incorporation by some skilled community members here? I am just a python hobbyist loving the Dash universe

Cheers!

1 Like

Hi,

I attempted to create a dash component from reactflow, the basic node and edge creation just works fine but I got stuck in creating custom node and edges in reactflow.

My repository for dash reactflow : GitHub - siddharthajuprod07/dash-react-flow

The main issue I am facing is when I pass a dash component (like dcc.checkbox etc) as part of the custom node react is not able to identify it as a component and not rendering it.

I am following the same guide mentioned in reactflow custom node (Custom Nodes Guide - React Flow)

I have develop the code to handle the components passed from dash app (https://github.com/siddharthajuprod07/dash-react-flow/blob/master/src/ts/utils/customNode.tsx) , but its throwing me error.

import React, { useCallback } from 'react';
import { Handle, Position } from 'reactflow';


function CustomNode({ data, isConnectable }) {
  const dashComponentList:React.ReactNode[] = data["dashComponents"]
  console.log(dashComponentList)

  function createReactComponent(dashComponent:React.ReactNode,i:number){
    const NewComponent = dashComponent["namespace"]
    return <NewComponent key={i} type={dashComponent["type"]} {...dashComponent["props"]}></NewComponent>
  }
  
  return (
      <div className="custom-node">
        <Handle type="target" position={Position.Top} isConnectable={isConnectable} />
        {dashComponentList.map((dashComponent,i) => createReactComponent(dashComponent,i))} 
        <Handle type="source" position={Position.Bottom} isConnectable={isConnectable} />
      </div> 
  );
}

export default CustomNode;

Can anybody give me any direction on how to pass dash components to react to identify it as a component and render it?

Thanks,
Sid

@snehilvj will you be able to provide some guidence here please.

With the influx of new code generation tools (specifically the new Claude Sonnet 3.5 v2), wonder if it’s somewhat easier to port react libraries to plotly dash as components? If so, hopefully we can bring react-flow to plotly dash

@Sid Were you able to resolve the error you were facing?

Echoing what @monitorcasalab said, these component would be a great addition to Dash. @AnnMarieW @jinnyzor @snehilvj Any help would be greatly appreciated.

Its very easy to build components with Claude try it yourself, created a video a week or two ago on this subject that you could use as a guide.

1 Like

I was actually also thinking of making it a component. It’s somewhat on the list but still other parts which are more urgent, but I do think it has many features we like.

I did convert some smaller parts before, maybe I will use @PipInstallPython guide and work on this :smiley: But happy to join hands here

So I followed the video by @PipInstallPython (thank you so much for this btw!) and was able to get a basic react flow component to run.

Next, I want to implement the whole reactflow library and converted their entire documentation and reference API into PDFs. But the issue is that I have a free Claude.AI account and I am hitting “conversation over length limit” errors when trying to attach the PDFs.

Placing the PDFs here if someone wants to utilize:
https://drive.google.com/drive/folders/11l1x7MWpE920cBjnt1VQKF90AZpfJSvL?usp=sharing

Do drop an update if able to make progress with this. @PipInstallPython @simon-u

1 Like

Thats awesome! Thanks for the update it made my day, happy the video helped and its look really good! I can see this being a really useful component and its nice to see other component builders starting their journey and building useful tools for this great framework :smiley:

@hkhare , do you have a repo to share? Then I can see what you already have and can start from there

I have it here: GitHub - hkhare42/dash-flow-dev: dash component based on react flow but I’ve managed to break something trying to implement custom edges, custom nodes and dagre layouting. Figuring it out. Feel free to fork.

Your layout looks somewhat wrong, not sure what this folder is. Assuming you messed up and didn’t delete it for some reason?

Gave it a shot and was able to get it working and fixed the errors that are currently visibale in the repo you sent, ended up being a bit different than the approach you went with but I’ll include a demo and reffrence.

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import {
    ReactFlow,
    Controls,
    MiniMap,
    Background,
    addEdge,
    ReactFlowProvider,
    applyNodeChanges,
    applyEdgeChanges
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';

/**
 * Convert Dash components in node data to React elements
 */
const processDashComponents = (nodes) => {
    if (!nodes) return [];
    return nodes.map(node => ({
        ...node,
        data: {
            ...node.data,
            label: node.data.label?.props ? node.data.label : String(node.data.label || '')
        }
    }));
};
/**
 * DashFlow is a Dash component that wraps React Flow to create
 * interactive node-based interfaces. It supports customizable nodes,
 * edges, and various interaction modes.
 */
const Flow = (props) => {
    const {
        id,
        nodes,
        edges,
        nodesDraggable,
        nodesConnectable,
        elementsSelectable,
        nodeTypes,
        edgeTypes,
        showMiniMap,
        showControls,
        showBackground,
        style,
        className,
        setProps
    } = props;

    // Process nodes to handle Dash components
    const processedNodes = processDashComponents(nodes);

    const onNodesChange = useCallback((changes) => {
        // Use applyNodeChanges helper from React Flow to correctly update nodes
        const nextNodes = applyNodeChanges(changes, nodes);
        setProps({ nodes: nextNodes });
    }, [nodes, setProps]);

    const onEdgesChange = useCallback((changes) => {
        // Use applyEdgeChanges helper from React Flow to correctly update edges
        const nextEdges = applyEdgeChanges(changes, edges);
        setProps({ edges: nextEdges });
    }, [edges, setProps]);

    const onConnect = useCallback((connection) => {
        if (!connection.source || !connection.target) return;
        const newEdge = {
            id: `e${connection.source}-${connection.target}`,
            ...connection
        };
        setProps({ edges: [...edges, newEdge] });
    }, [edges, setProps]);

    // Default container style with mandatory height
    const containerStyle = {
        width: '100%',
        height: '600px',  // Set a default height
        ...style
    };

    return (
        <div id={id} style={containerStyle} className={className}>
            <ReactFlow
                nodes={processedNodes}
                edges={edges}
                onNodesChange={onNodesChange}
                onEdgesChange={onEdgesChange}
                onConnect={onConnect}
                nodesDraggable={nodesDraggable}
                nodesConnectable={nodesConnectable}
                elementsSelectable={elementsSelectable}
                nodeTypes={nodeTypes}
                edgeTypes={edgeTypes}
                fitView
                deleteKeyCode={['Backspace', 'Delete']}
                panOnScroll
                selectionOnDrag
                panOnDrag={[1, 2]}
                zoomOnScroll
                snapToGrid
            >
                {showControls && <Controls />}
                {showMiniMap && <MiniMap />}
                {showBackground && <Background />}
            </ReactFlow>
        </div>
    );
};


// Main component that wraps Flow with ReactFlowProvider
const DashFlow = (props) => {
    return (
        <ReactFlowProvider>
            <Flow {...props} />
        </ReactFlowProvider>
    );
};

DashFlow.defaultProps = {
    nodesDraggable: true,
    nodesConnectable: true,
    elementsSelectable: true,
    showMiniMap: true,
    showControls: true,
    showBackground: true,
    nodes: [],
    edges: [],
    style: {},
    className: ''
};

DashFlow.propTypes = {
    /**
     * The ID used to identify this component in Dash callbacks.
     */
    id: PropTypes.string,

    /**
     * Array of node objects with position, data, and optional style information
     */
    nodes: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.string.isRequired,
        position: PropTypes.shape({
            x: PropTypes.number.isRequired,
            y: PropTypes.number.isRequired
        }).isRequired,
        data: PropTypes.object.isRequired,
        type: PropTypes.string,
        style: PropTypes.object
    })),

    /**
     * Array of edge objects defining connections between nodes
     */
    edges: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.string.isRequired,
        source: PropTypes.string.isRequired,
        target: PropTypes.string.isRequired,
        type: PropTypes.string,
        animated: PropTypes.bool,
        style: PropTypes.object
    })),

    /**
     * Enable/disable node dragging
     */
    nodesDraggable: PropTypes.bool,

    /**
     * Enable/disable creating new connections
     */
    nodesConnectable: PropTypes.bool,

    /**
     * Enable/disable selection
     */
    elementsSelectable: PropTypes.bool,

    /**
     * Custom node type components
     */
    nodeTypes: PropTypes.object,

    /**
     * Custom edge type components
     */
    edgeTypes: PropTypes.object,

    /**
     * Show/hide minimap
     */
    showMiniMap: PropTypes.bool,

    /**
     * Show/hide controls
     */
    showControls: PropTypes.bool,

    /**
     * Show/hide background
     */
    showBackground: PropTypes.bool,

    /**
     * Custom style for the container div
     */
    style: PropTypes.object,

    /**
     * Custom CSS class name
     */
    className: PropTypes.string,

    /**
     * Dash-assigned callback that should be called to report property changes
     * to Dash, to make them available for callbacks.
     */
    setProps: PropTypes.func
};

// Add this to register the component name
DashFlow.displayName = 'DashFlow';

export default DashFlow;

My advice would be together all the notable project knowledge you can acquire. I scrubbed all Quickstart - React Flow, Api Reference - React Flow and React Flow.

Then I asked it to build me a roadmap.md for the project and used it for custom instructions for project Then just asked questions like in the video tuttorial.

Thanks for sharing.

Yeah don’t know how or why that folder is there but I let it stay since I didn’t want to mess with the boilerplate structure. It’s probably there because of an erroneous run.

Main component code is at : dash-flow-dev/dash_flow/src/lib/components at main · hkhare42/dash-flow-dev · GitHub

The simple node-edge setup worked for me. It’s when I started to implement custom nodes, custom edges and dagre layouting that I ran into problems. And all of these three are essential for what I’m working on. Something that looks like:

Looking forward to your demo / reference to learn more.

Not entirely to that point in the image you shared. Current limitations are it only supporting html components within nodes. Was working on a way to other dash components to render into nodes but wasn’t able to fully figure it out. Along with not being able to figure out edge animations like in Animating Edges - React Flow

I did set this build up with a mini map prop, debug tool and was able to get resizable nodes working.

1 Like

Just ran through the Dash-Flow-Components its nice to see someone start on this 2yrs prior to this thread but the code is not a full wrap of the React Flow and is limited in capability.

Compared the props setup between this project Dash-Flow-Components and the repo I made Dash-Flow the Dash-Flow is much more up to date to build from at this current state.

looks like Dash-Flow-Components is based on Release 11.5.1

where the repo I made Dash-Flow is based on Release @xyflow/react@12.3.5 · xyflow/xyflow · GitHub

Was able to make strides in setting up a:

  • Debug mode
  • ability to handle other components inside a Flow component (like adding an html picture for example)
  • Background grid
  • while also supporting all functionality of the other repositories mentioned

Not really interested in building this fully out, but both repos as well as looking through @hkhare’s code could be useful in a full wrap of this project. The issue I have is with taking this on is how extensive the options and props are with React Flow, its clearly possible as 3 separate people have built out a proof of concept. But to properly design a completed wrap and document would require a fair bit of free time. Interesting stuff and library all around and hope someone picks up the peaces laid out in this thread and builds this out till completion.

1 Like

Hi, as of now the Dash-Flow getting started example is failing out of the box. The dash_flow.DashFlow() args are invalid and even after them I get more errors:

Woops, had it working pretty good. Think when I went to publish it as an npm install the name dash-flow was taken so I attempted to manually change it to dash-flows and publish and I think thats what broke the component.

I’ll work on a fix this week and will update you when its all resolved.

1 Like

Just pushed out a 0.0.3 release, should be able to pip install dash-flows now and it will work. Wouldn’t call it a polished release but it’s a starting point. You can use any dash html component within a node. Also supports the re-alignment horizontal or vertical. Along with animated nodes.

import dash
from dash import html, Input, Output, State, clientside_callback, _dash_renderer, dcc
import dash_flows
import dash_mantine_components as dmc


_dash_renderer._set_react_version("18.2.0")

app = dash.Dash(__name__, assets_folder='assets', external_stylesheets=dmc.styles.ALL)

initial_nodes = [
    # First node stays the same
    {
        'id': '1',
        'type': 'resizable',
        'data': {
            'label': html.Div([
                html.Img(src="https://avatars.githubusercontent.com/u/120129682?v=4",
                         style={'width': '100%', 'height': '100%'}),
            ], style={
                'display': 'flex',
                'flexDirection': 'column',
                'alignItems': 'center',
                'gap': '10px',
                'padding': '10px'
            })
        },
        'position': {'x': 250, 'y': 25},
        'style': {
            'width': 300,
            'height': 300,
        }
    },
    # Second node (static)
    {
        'id': '2',
        'type': 'resizable',
        'data': {'label': html.Div([
            html.Img(src="https://avatars.discourse-cdn.com/v4/letter/h/50afbb/288.png",
                     style={'width': '100%', 'height': '100%'}),
        ], style={
            'display': 'flex',
            'flexDirection': 'column',
            'alignItems': 'center',
            'gap': '10px',
            'padding': '10px'
        })},
        'position': {'x': 250, 'y': 150},
        'style': {
            'width': 300,
            'height': 300,
        }
    },
    # Add an animated node
    {
        'id': 'animated1',
        'type': 'circle',
        'data': {'label': '🔄'},
        'position': {'x': 250, 'y': 150},
        'style': {
            'width': 60,
            'height': 60,
        }
    },
    # Third node stays the same
    {
        'id': '3',
        'type': 'resizable',
        'data': {
            'label': html.Div([
html.Button(id='btn_example', children='button')], style={
            'display': 'flex',
            'flexDirection': 'column',
            'alignItems': 'center',
            'gap': '10px',
            'padding': '10px'
        })
        },
        'position': {'x': 250, 'y': 250},
        'style': {
            'width': 300,
            'height': 300,
            'minHeight': 200
        }
    }
]

initial_edges = [
    {
        'id': 'e1-2',
        'source': '1',
        'target': '2',
        'type': 'animated',
        'data': {
            'animatedNode': 'animated1'  # Reference the dedicated animated node
        },
        'style': {
            'strokeWidth': 2,
            'stroke': '#555'
        }
    },
    {
        'id': 'e2-3',
        'source': '2',
        'target': '3',
        'type': 'default',  # Changed to default type
        'style': {
            'strokeWidth': 2,
            'stroke': '#555'
        }
    }
]


# Add layout buttons above the DashFlow component
layout_buttons = dmc.Group([
    dmc.Button("Vertical Layout", id="btn-vertical", variant="outline"),
    dmc.Button("Horizontal Layout", id="btn-horizontal", variant="outline"),
    dmc.Button("Radial Layout", id="btn-radial", variant="outline"),
    dmc.Button("Force Layout", id="btn-force", variant="outline"),
], mt="md", mb="md")

app.layout = dmc.MantineProvider([
    layout_buttons,
    dash_flows.DashFlows(
        id='react-flow-example',
        nodes=initial_nodes,
        edges=initial_edges,
        showDevTools=True,
        style={'height': '600px'},
        layoutOptions=None  # Add this prop
    ),
    # Hidden div for storing layout options
    html.Div(id='layout-options', style={'display': 'none'})
])

# Create a clientside callback to handle layout changes
app.clientside_callback(
    """
    function(n_vertical, n_horizontal, n_radial, n_force) {
        const triggered = dash_clientside.callback_context.triggered[0];
        if (!triggered) return window.dash_clientside.no_update;

        const btnId = triggered.prop_id.split('.')[0];
        let options = {};

        switch(btnId) {
            case 'btn-vertical':
                options = {
                    'elk.algorithm': 'layered',
                    'elk.direction': 'DOWN',
                    'elk.spacing.nodeNode': 80,
                    'elk.layered.spacing.nodeNodeBetweenLayers': 100
                };
                break;
            case 'btn-horizontal':
                options = {
                    'elk.algorithm': 'layered',
                    'elk.direction': 'RIGHT',
                    'elk.spacing.nodeNode': 80,
                    'elk.layered.spacing.nodeNodeBetweenLayers': 100
                };
                break;
            case 'btn-radial':
                options = {
                    'elk.algorithm': 'org.eclipse.elk.radial',
                    'elk.radial.radius': 200
                };
                break;
            case 'btn-force':
                options = {
                    'elk.algorithm': 'org.eclipse.elk.force',
                    'elk.force.iterations': 300,
                    'elk.spacing.nodeNode': 80
                };
                break;
            default:
                return window.dash_clientside.no_update;
        }
        return JSON.stringify(options);
    }
    """,
    Output('react-flow-example', 'layoutOptions'),  # Change output target to DashFlow's layoutOptions
    Input('btn-vertical', 'n_clicks'),
    Input('btn-horizontal', 'n_clicks'),
    Input('btn-radial', 'n_clicks'),
    Input('btn-force', 'n_clicks'),
    prevent_initial_call=True
)

if __name__ == '__main__':
    app.run_server(debug=True, port=7777)

had to move the repo: GitHub - pip-install-python/dash-flows: React-flows brought over to the dash framework.

1 Like

Thanks, it works great and what you’ve done is really useful! I will play with it in the next few days, do you mind if I ask you in case I have some further questions?

Shoot away, not sure If I’ll have answers for every question you might have but I’ll try my best.