Introduction
To create a mapping tool, we need to deal with a lot of canvas or block elements from html. React community has developed a library, reactflow as an alternative to developing flows which are based on nodes.
Concepts
React Flow
Node: A node is a block that can be dragged. A Node can be connected with other nodes. A node needs a position and a label.
Edge: An edge is a connection between two nodes. An edge needs a source (node id) and a target (node id).
Handle: A handle is a kind of port of a node that is used to connect nodes. You start a connection at a handle and end it at one another.
Connection Line: The connection line is the line that gets drawn while you connect two nodes with each other.
Transform: Used to describe the current viewport of the pane. It's an array with three numbers
[x, y, zoom]
Important considerations
React Flow can be controlled or uncontrolled flow, react flow recommends using controlled flow.
The dimensions of your React Flow component depend on the parent dimensions.
zustand
Is yet another state management library and the major difference is this can be used without React.
It exposes hooks (action creators), to manage the state of your application.
Creating application
setup reactjs app
Install React Flow & zustand
Create a application state
- Create a state directory
- create a file named
nodes.ts
within state directory - Create sample nodes
import { Node } from 'react-flow-renderer';
const nodes :Node[] = [
{
id: '1',
type: 'input',
data: { label: 'Input' },
position: { x: 250, y: 25 },
},
{
id: '2',
data: { label: 'Default' },
position: { x: 100, y: 125 },
},
{
id: '3',
type: 'output',
data: { label: 'Output' },
position: { x: 250, y: 250 },
},
];
export default nodes
- Create a file named
edges.ts
within state directory - Create connecting lines between previously defined nodes.
import { Edge } from 'react-flow-renderer';
const edges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3' },
] ;
export default edges
- Create application reducers and selectors using zustand
import create from "zustand";
import {
Connection,
Edge,
EdgeChange,
Node,
NodeChange,
addEdge,
OnNodesChange,
OnEdgesChange,
OnConnect,
applyNodeChanges,
applyEdgeChanges,
} from "react-flow-renderer";
import initialNodes from "./nodes";
import initialEdges from "./edges";
export type NodeData = {
color: string;
text?: string;
};
type RFState = {
nodes: Node[];
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
onConnect: OnConnect;
addNode: (node: Node<NodeData>) => void;
};
const useStore = create<RFState>((set, get) => ({
nodes: initialNodes,
edges: initialEdges,
onNodesChange: (changes: NodeChange[]) => {
set({
nodes: applyNodeChanges(changes, get().nodes),
});
},
onEdgesChange: (changes: EdgeChange[]) => {
set({
edges: applyEdgeChanges(changes, get().edges),
});
},
onConnect: (connection: Connection) => {
set({
edges: addEdge(connection, get().edges),
});
},
addNode(node: Node<NodeData>) {
set({
nodes: [...get().nodes, node],
});
},
}));
export default useStore;
Consuming app state using React Flow
- Create Wrapper components
import React from "react";
import ReactFlow from "react-flow-renderer";
import useStore from "../state/store";
const Wrapper = () => {
const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useStore();
return (
<div style={{ height: "100vh" }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
</div>
);
};
export default Wrapper;
- Import it in
App
import React from 'react';
import './App.css';
import Wrapper from './components/Wrapper';
function App() {
return (
<div className="App">
<Wrapper />
</div>
);
}
export default App;
Adding a custom node to your app
- Create a custom component
import React, { FC, useCallback } from "react";
import { Handle, Position, NodeProps } from "react-flow-renderer";
import { NodeData } from "../state/store";
const InputNode: FC<NodeProps<NodeData>> = ({ data, id }) => {
return (
<div style={{ background: "#9ca8b3", padding: "10px" }}>
<Handle type="target" position={Position.Left} id={`${id}.left`} />
<div id={id}>{data.text}</div>
<Handle type="source" position={Position.Right} id={`${id}.right1`} />
</div>
);
};
export default InputNode;
- add them in nodeTypes for
ReactFlow
component
const nodeTypes: NodeTypes = {
customInput: InputNode,
};
- create
addNewNode
function withinWrapper
component
const addNewNode = useCallback(() => {
const newNode: Node<NodeData> = {
id: `${getUniqueId(10)}`,
data: { color: `red` },
type: "customInput",
position: {
x: 100,
y: 100,
},
style: {
width: 150,
},
};
addNode(newNode);
}, [addNode]);
Change our custom node to take input from user, and update app state.
- Add a new reducer in our
store.js
file
updateNode(nodeId, text) {
set({
nodes: get().nodes.map((node) => {
if (node.id === nodeId) {
return { ...node, data: { ...node.data, text } };
}
return node;
}),
});
},
- Change
div
element intoinput
type and add anonChange
event handler
const onChange = useCallback(
(evt: ChangeEvent<HTMLInputElement>) => {
updateNode(id, evt.target.value);
},
[id, updateNode]
);
return <>
<input
type="text"
onChange={onChange}
id={id}
style={{ width: "100%", flex: 1 }}
/>
</>
Now you will be able to add a node and add or modify the text on it.
Notes
Some of the steps here are taken from reactflow.dev, you can refer to the original documentation if you need more information.
Source code can be found here
Top comments (0)