Coder Social home page Coder Social logo

Comments (11)

bogris avatar bogris commented on July 19, 2024 1

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.

bogris avatar bogris commented on July 19, 2024 1

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.

bogris avatar bogris commented on July 19, 2024 1

this is not an issue anymore on next15

from xyflow.

moklick avatar moklick commented on July 19, 2024

I can't reproduce this. Are you sure that you are not mutating the updatedNodes somewhere?

from xyflow.

moklick avatar moklick commented on July 19, 2024

any update on this @bogris ?

from xyflow.

bogris avatar bogris commented on July 19, 2024

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.

bogris avatar bogris commented on July 19, 2024

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.

bogris avatar bogris commented on July 19, 2024

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.

sroussey avatar sroussey commented on July 19, 2024

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.

bcakmakoglu avatar bcakmakoglu commented on July 19, 2024

Why not use useNodesInitialized for layouting?

from xyflow.

bogris avatar bogris commented on July 19, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.