Creating Custom Nodes in React Flow: Building a Decision Maker Web App
React Flow is a powerful library for building node-based applications, offering a flexible and customizable environment for creating complex workflows and interactive diagrams. In this article, we will explore how to create custom nodes in React Flow and build a decision maker web app. This app will allow users to create and connect nodes to make decisions based on various conditions.
Prerequisites
Before we start, ensure you have the following installed:
Node.js and npm (Node Package Manager)
A code editor like Visual Studio Code
Setting Up the Project
First, create a new React project using Create React App:
npm create-react-app reactflow-decision-maker
cd reactflow-decision-maker
Next, install React Flow and its dependencies:
npm install react-flow-renderer
Basic Setup of React Flow
Open src/App.js
and set up a basic React Flow diagram:
import React from 'react';
import ReactFlow, { addEdge, MiniMap, Controls, Background } from 'react-flow-renderer';
const initialElements = [
{ id: '1', type: 'input', data: { label: 'Start' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Decision Node' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'End' }, position: { x: 250, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3' },
];
const App = () => {
const [elements, setElements] = React.useState(initialElements);
const onConnect = (params) => setElements((els) => addEdge(params, els));
return (
<div style={{ height: 600 }}>
<ReactFlow
elements={elements}
onConnect={onConnect}
snapToGrid={true}
snapGrid={[16, 16]}
style={{ background: '#A2B0E8' }}
>
<MiniMap />
<Controls />
<Background color="#888" gap={16} />
</ReactFlow>
</div>
);
};
export default App;
Creating Custom Nodes
To create custom nodes, we'll define new node types and specify their rendering logic. Create a new file src/CustomNodes.js
:
import React from 'react';
export const DecisionNode = ({ data }) => {
return (
<div style={{ padding: 10, border: '1px solid black', borderRadius: 5, background: '#fff' }}>
<div>{data.label}</div>
<div>
<button style={{ margin: '5px' }} onClick={() => data.onDecision('yes')}>Yes</button>
<button style={{ margin: '5px' }} onClick={() => data.onDecision('no')}>No</button>
</div>
</div>
);
};
In this custom node, we define a decision node with two buttons: "Yes" and "No". The button clicks trigger the onDecision
function passed in the node's data.
Next, integrate this custom node into the main application. Update src/App.js
:
import React from 'react';
import ReactFlow, { addEdge, MiniMap, Controls, Background, ReactFlowProvider } from 'react-flow-renderer';
import { DecisionNode } from './CustomNodes';
const nodeTypes = {
decision: DecisionNode,
};
const initialElements = [
{ id: '1', type: 'input', data: { label: 'Start' }, position: { x: 250, y: 5 } },
{ id: '2', type: 'decision', data: { label: 'Decision Node', onDecision: (decision) => alert(`Decision: ${decision}`) }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'End' }, position: { x: 250, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3' },
];
const App = () => {
const [elements, setElements] = React.useState(initialElements);
const onConnect = (params) => setElements((els) => addEdge(params, els));
return (
<div style={{ height: 600 }}>
<ReactFlowProvider>
<ReactFlow
elements={elements}
onConnect={onConnect}
snapToGrid={true}
snapGrid={[16, 16]}
style={{ background: '#A2B0E8' }}
nodeTypes={nodeTypes}
>
<MiniMap />
<Controls />
<Background color="#888" gap={16} />
</ReactFlow>
</ReactFlowProvider>
</div>
);
};
export default App;
Adding Node Interaction
To make our decision-making process more interactive, we need to handle the decisions dynamically. Modify the src/CustomNodes.js
:
import React from 'react';
export const DecisionNode = ({ data }) => {
const handleDecision = (decision) => {
data.onDecision(decision);
if (data.onDecisionComplete) data.onDecisionComplete(decision);
};
return (
<div style={{ padding: 10, border: '1px solid black', borderRadius: 5, background: '#fff' }}>
<div>{data.label}</div>
<div>
<button style={{ margin: '5px' }} onClick={() => handleDecision('yes')}>Yes</button>
<button style={{ margin: '5px' }} onClick={() => handleDecision('no')}>No</button>
</div>
</div>
);
};
Update src/App.js
to handle decision completion:
import React from 'react';
import ReactFlow, { addEdge, MiniMap, Controls, Background, ReactFlowProvider, updateEdge } from 'react-flow-renderer';
import { DecisionNode } from './CustomNodes';
const nodeTypes = {
decision: DecisionNode,
};
const initialElements = [
{ id: '1', type: 'input', data: { label: 'Start' }, position: { x: 250, y: 5 } },
{
id: '2',
type: 'decision',
data: {
label: 'Decision Node',
onDecision: (decision) => console.log(`Decision: ${decision}`),
onDecisionComplete: (decision) => alert(`Decision completed: ${decision}`)
},
position: { x: 100, y: 100 }
},
{ id: '3', data: { label: 'End' }, position: { x: 250, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3' },
];
const App = () => {
const [elements, setElements] = React.useState(initialElements);
const onConnect = (params) => setElements((els) => addEdge(params, els));
return (
<div style={{ height: 600 }}>
<ReactFlowProvider>
<ReactFlow
elements={elements}
onConnect={onConnect}
snapToGrid={true}
snapGrid={[16, 16]}
style={{ background: '#A2B0E8' }}
nodeTypes={nodeTypes}
>
<MiniMap />
<Controls />
<Background color="#888" gap={16} />
</ReactFlow>
</ReactFlowProvider>
</div>
);
};
export default App;
In this code, the decision node now calls onDecisionComplete
after a decision is made, allowing you to handle the decision's outcome and possibly trigger further actions, such as connecting to other nodes or updating the node state.
Conclusion
You've now built a simple decision maker web app using React Flow with custom nodes. React Flow's flexibility allows you to create highly interactive and complex applications. This guide provides a foundation for you to explore and expand your node-based applications further. Experiment with additional features and customizations to tailor the application to your specific needs. Happy coding!
Last updated