Comments (11)
sorry for the delay.
This is part of a convoluted function that does node positioning while dragging and on drag stop.
at first I hoped for a miracle from your side before me digging into it again.
In the mean time I got sidetracked and did not got to this one.
I think it's better to close this, and I'll get back with some feedback after I fugure it out.
from xyflow.
another update.
Altough it seemed like a good workaround to manipulate the Zustand strore directly, this introduces a bug:
By not using rFinstance.addNodes function, I don't generate the "add" change event and it breakes the layouting logic as I tried to build it around the changes that are fired.
from xyflow.
this is not an issue anymore on next15
from xyflow.
I can't reproduce this. Are you sure that you are not mutating the updatedNodes
somewhere?
from xyflow.
any update on this @bogris ?
from xyflow.
hi @moklick
This one was really bugging me for leaving it "in the air"...
I managed to isolate the behaiviour in the code below.
after adding the node it will be moved back to position 0, before the parent, so if the parent as BG, the child will be "behind"
In order to easly "work" with this, the custom node has transparent bg by default, but it is solid on selection so you can observe the problem.
I also logged the array of node in the .setNodes, and after the change is processed by rfInstance.
"use client";
import { Button } from "@radix-ui/themes";
import {
ReactFlowInstance,
ReactFlow,
Node,
useEdgesState,
useNodesState,
Panel,
NodeProps,
Background,
} from "@xyflow/react";
import { clsx, ClassValue } from "clsx";
import { FC, useEffect, useState } from "react";
import "reactflow/dist/base.css";
import { twMerge } from "tailwind-merge";
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
export const CustomNodeOne: FC<NodeProps<any>> = (props) => {
const { id, selected, width, height, data } = props;
return (
<>
{/* <NodeResizer/> */}
<div
className={cn("flex flex-col rounded border border-gray-600 p-4", {
"bg-yellow-100": selected,
})}
style={{
height: data.height,
width: data.width,
}}
>
<h1 className="text-lg font-bold">{data.label}</h1>
<h1 className="text-xs text-gray-400">{id}</h1>
<p className="text-gray-899 text-xs">
H: {height || "null"} W: {width || "null"}
</p>
</div>
</>
);
};
const nodeTypes = {
"custom-one": CustomNodeOne,
} as const;
const mockInitialNodes: Node[] = [
// one parent for "1", "2"
{
id: "0",
data: {
label: "Test node 0",
width: 500,
height: 500,
},
position: { x: 200, y: 200 },
type: "custom-one",
// type: "leaf-big",
},
{
id: "1",
parentNode: "0",
data: {
label: "Test node 0-1",
with: 200,
height: 100,
},
position: { x: 50, y: 100 },
type: "custom-one",
},
];
export default function Dashboard3Page() {
const [nodes, setNodes, onNodesChange] = useNodesState<Node<any>>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]);
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
const addChildNode = async () => {
console.log("nodes: ", nodes);
const newNode: Node<any> = {
id: Math.random().toString(36).substring(7),
data: {
label: "Test node 0-2",
with: 200,
height: 100,
},
parentNode: "0",
extent: "parent",
position: { x: 350, y: 100 },
type: "custom-one",
};
rfInstance?.setNodes((ns) => {
const newNodes = [...ns, newNode];
//log nodes before:
console.log("addChildNode: newNodes before: ", newNodes);
return newNodes;
});
//delay
await new Promise((resolve) => setTimeout(resolve, 1000));
//log nodes after:
console.log("addChildNode: nodes after: ", rfInstance?.getNodes());
};
useEffect(() => {
if (rfInstance) {
rfInstance.setNodes(mockInitialNodes);
}
}, [rfInstance]);
return (
<div className="fixed inset-0 top-[24px] bg-background">
<div className="relative h-[calc(100vh_-_24px)] text-black">
<div style={{ height: "100%" }}>
<ReactFlow
{...{
edges,
nodes,
onNodesChange,
onEdgesChange,
onInit: setRfInstance,
nodeTypes,
}}
>
<Panel className="flex gap-4 p-4">
<Button onClick={addChildNode}>add child</Button>
</Panel>
<Background />
</ReactFlow>
</div>
</div>
</div>
);
}
I am using the app directory in nextJs.
the relevant package versions are:
"@xyflow/react": "^12.0.0-next.12",
"next": "14.0.4",
"@radix-ui/themes": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
I hope is a better report this time :D
from xyflow.
Qwick update:
I relaceed
// useEdgesState,
// useNodesState,
with a zustand implementation and it works as expected:
import { create } from 'zustand';
import {
Connection,
Edge,
EdgeChange,
Node,
NodeChange,
addEdge,
OnNodesChange,
OnEdgesChange,
OnConnect,
applyNodeChanges,
applyEdgeChanges,
} from "@xyflow/react";
// import initialNodes from './nodes';
// import initialEdges from './edges';
export type RFState = {
nodes: Node[];
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
onConnect: OnConnect;
setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void;
addNode: (node: Node) => void;
};
const mockInitialNodes: Node[] = [
// one parent for "1", "2"
{
id: "0",
data: {
label: "Test node 0",
width: 500,
height: 500,
},
position: { x: 200, y: 200 },
type: "custom-one",
// type: "leaf-big",
},
{
id: "1",
parentNode: "0",
data: {
label: "Test node 0-1",
with: 200,
height: 100,
},
position: { x: 50, y: 100 },
type: "custom-one",
},
];
// this is our useStore hook that we can use in our components to get parts of the store and call actions
export const useFlowStore = create<RFState>((set, get) => ({
nodes: mockInitialNodes,
edges: [],
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),
});
},
setNodes: (nodes: Node[]) => {
set({ nodes });
},
setEdges: (edges: Edge[]) => {
set({ edges });
},
addNode: (node: Node) => {
set((state) => ({ nodes: [...state.nodes, node] }));
}
}));
and th eupdates in the flow:
export default function Dashboard3Page() {
const { nodes, edges, onNodesChange, onEdgesChange, onConnect, addNode } = useFlowStore(selector, shallow);
const [rfInstance, setRfInstance] = useState<ReactFlowInstance | null>(null);
const addChildNode = async () => {
console.log("nodes: ", nodes);
const newNode: Node<any> = {
id: Math.random().toString(36).substring(7),
data: {
label: "Test node 0-2",
with: 200,
height: 100,
},
parentNode: "0",
extent: "parent",
position: { x: 350, y: 100 },
type: "custom-one",
};
addNode(newNode);
// rfInstance?.setNodes((ns) => {
// const newNodes = [...ns, newNode];
// //log nodes before:
// console.log("addChildNode: newNodes before: ", newNodes);
// return newNodes;
// });
// //delay
// await new Promise((resolve) => setTimeout(resolve, 1000));
// //log nodes after:
// console.log("addChildNode: nodes after: ", rfInstance?.getNodes());
};
return (
<div className="fixed inset-0 top-[24px] bg-background">
<div className="relative h-[calc(100vh_-_24px)] text-black">
<div style={{ height: "100%" }}>
<ReactFlow
{...{
edges,
nodes,
onNodesChange,
onEdgesChange,
onConnect,
onInit: setRfInstance,
nodeTypes,
}}
>
<Panel className="flex gap-4 p-4">
<Button onClick={addChildNode}>add child</Button>
</Panel>
<Background />
</ReactFlow>
</div>
</div>
</div>
);
}
this is the zustand implementation
from xyflow.
i was working with collab functions and I got to this error again.
It was caused now by using
rfInstance.addNode(...)
in order to reproduce it, in the code above with zusand, replace the store provided addNode with rfInstance.addNode(...)
from xyflow.
By not using rFinstance.addNodes function, I don't generate the "add" change event and it breakes the layouting logic as I tried to build it around the changes that are fired.
we have the same issue in ngraph
from xyflow.
Why not use useNodesInitialized
for layouting?
from xyflow.
Well... first, i was not aware of it :))
It would have saved me some hours last tear if I read the docs properly 😔
In my implementation i also do stuff on drag, hide and delete. I have a simulated flex box behavior for children where i control the layout with tailwind like classnames key in data (gap-2 spaces children at 8px for example, p-3 spaces the children 12px from the sides of the parent, etc). So add is just a small part.
I also start the updates as a tree from the node that changed through his parents.
I ended up using onNodeChange to centralize the point where i apply the logic.
I will say that today i got to a workaround as i am hooking on the dimension change event. Looks like a side-effect of the node measures. So maybe the mentioned hook would have also worked, but i try my best to keep logic in one place.
To add to this, i also implemented collaboration, and that is also linked in onNodeChange.
from xyflow.
Related Issues (20)
- There is an error in client site
- Removing extent attribute onNodeDragStart doesn't allows for free roam until after onNodeDragStop HOT 7
- Multiple dragging doesn't trigger onNodeDragStop event. HOT 2
- Class for connecting from node HOT 1
- How automatically enabled the lock viewport of controls in ReactFlow HOT 1
- SvelteFlow: Copy/Paste (If not supported out-of-the-box, the easiest way to achieve it) HOT 6
- Source and Target handle getting null HOT 5
- Working Reactflow, when i add a Panel it errors HOT 1
- Drag problem after changing parentNode HOT 1
- SvelteFlow: Copy/Paste (continued issue) HOT 1
- Be able to pan the viewport on right mouse click on Node. HOT 6
- Disable node drag on multitouch HOT 3
- Remove pointer events from Pane if user selection active HOT 1
- Not possible to deselect edges HOT 1
- Edges not initially rendered onlyRenderVisibleElements is set and connecting offscreen node has height set HOT 1
- @types packages should be devDependencies HOT 1
- In SvelteFlow, setting viewport on initialization gets overridden on pan HOT 1
- Dynamic edge label updates not working HOT 1
- Rename `updateEdge` to `reconnectEdge` HOT 5
- Using Jest to click on handle throws "S?.elementFromPoint is not a function" error HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from xyflow.