#Validation by type

1 messages ยท Page 1 of 1 (latest)

shadow meteor
#

Hi everyone, I'm toying with react-flow to build a custom logic graph. Let's say I have nodes for string manipulation (like concatenating two strings) and arithmetic operations (like adding two numbers). Each source and target handle have a defined type, meaning they produce/accept a string or a number.

How can I validate that I can't connect a "number" source with a "string" target? The handle's have different ids, so I can't use a generic id like "string" or "number" for validation in the "isValidConnection" method.

I was hoping that I could somehow attach more "metadat" to a handle beside the id that would be available in the "isValidConnection" method, but it seems I'm out of luck.

Any ideas?

south sorrel
#

You can have an Id like ${UNIQUE_ID}${SOME_SEPERATOR}${CUSTOM_DATA}. You can then seperate the handle Id by that seperator and get the custom data in the isValidConnection prop

shadow meteor
#

Thank you. That would work.

#

Nevertheless, if there is native solution that would allow to use more complex data, I would prefer that.

pearl skiff
#

There is no "native" solution to assign an additional type to a handle if you mean <Handle my-type="string" ...> or whatever

#

You could wrap the existing <Handle> component though, making your own CustomHandle that receives the type as a prop and then passes it down to it's own isValidConnection handler or something ๐Ÿ™‚

shadow meteor
#

Yes, but then I would still need to somehow tie my metadata to the specific port/handle. The logic of isValidConnection being a simple predicate is fine. Guess I will combine @south sorrel suggestion with a metadata store object.

pearl skiff
#

I'm not quite following the "metadata" issue unless you're using a global isValidConnection handler instead of binding it per <Handle> ๐Ÿค”

#

I'd think something like this would do it

import { Handle, HandleProps, Edge, Connection } from "@xyflow/react";

type DataType = "string" | "number";

interface CustomHandleProps extends Omit<HandleProps, "isValidConnection"> {
  dataType: DataType;
  isValidConnection: (edge: Edge | Connection, dataType: DataType) => boolean;
}

export const CustomHandle = (props: CustomHandleProps) => (
  <Handle
    {...props}
    isValidConnection={(...args) => {
      return props.isValidConnection?.(...args, props.dataType);
    }}
  />
);
south sorrel
shadow meteor
#

@pearl skiff I'm not sure I get it.

Given one node with source CustomHandle of data type "string" and one node with a target CustomHandle of data type "number". The isValidConnection of the source handle has no access to the data type of the target handle, right?

But that would be what I need, in pseudo-code:

isValidConnection = () => sourceHandle.dataType === targetHandle.dataType

Maybe I missunderstood your solution.

pearl skiff
#

Ah, now I get it - I wasn't sure what you were trying to accomplish until now ๐Ÿ˜„
My suggestion wouldn't quite do it for that use-case, that's right

I'm wondering if a handle type is bound to a node type?
So a string type Handle can only exist on a string type Node?
Or can they be mixed?

#

If they can't be mixed you could use useConnection since that provides the source and target nodes for an ongoing connection, which you could use in the isValidConnection callback

shadow meteor
#

Unfortunately, they can be mixed.

The whole concept of DataType only applies to a handle/port, not to a node. Basically the usecase is to allow the user to manipulate an input object through various transform nodes to defined target shape.

pearl skiff
#

Mh... I see, then you're gonna have the best luck by just adding the type into the handle id and reading it from there like

<Handle
  id={`${dataType}-${handleId}`
  ...
  isValidConnection={(connection) => {
    const targetDataType = connection.targetHandle?.split("-")[0];
    const sourceDataType = connection.sourceHandle?.split("-")[0];

    return targetDataType === sourceDataType
  }}
#

everything else would just be more work than necessary for this use-case imo ๐Ÿ˜„

shadow meteor
#

Okay. Thanks. This is the solution @south sorrel suggested. This is what I will do then.

pearl skiff
#

Good luck ๐Ÿ‘

#

One more idea that comes to mind though is saving handle info straight into node.data and reading it from there

{
  id: 'a',
  position: { ... },
  data: {
    handles: [
      {
        id: '1',
        dataType: 'string',
      },
      {
        id: '2',
        dataType: 'number',
      }
    ]
  }
}

Then you could read that out in isValidConnection by getting the node object and checking for the handles

const { getNode } = useReactFlow()

<Handle
  // ...
  isValidConnection={(connection) => {
    const targetNode = getNode(connection.target)
    const sourceNode = getNode(connection.source)

    if (targetNode && sourceNode) {
      const targetHandle = targetNode.data.handles.find(handle => handle.id === connection.targetHandle)
      const sourceHandle = /* same here */

      return targetHandle.dataType === sourceHandle.dataType
    }

    return false;
  }}

This might come more in handy if you need to compare more than just "dataType" at some point

shadow meteor
#

Thank you for your help and input. For now I decided against it, as the number of data types is finite and I would like to decouple the concept of type validation (e. g. which nodes are connectable) and the actual data the node holds.

I implemented the handle id based solution and it works fine. Although I think it would be a nice addition to allow for custom metadata to be attached to Handles. This would make a lot of use case simpler. For example I wanted to implement a max connection on a target handle where only one connection is allowed and when you connect a second node, it works, but drops the first connection. But it's a bit cumbersome. Great and powerful library nonetheless.

shadow meteor
#

Revisiting this. As my node editor evolves, I'm implementing nodes representing logical operators like if/else. Use case: An if/else node has four ports:

(1): Condition [boolean]
(2) True value
(3) False value
(4) Result

Ports 2 -4 must be of the same type. Initially they would accept any type, but as soon as one of these ports has a connection, the other ports should only accept the same type.

So one caveat with the handle id bases solution, where the value type is encoded in the handle id, is that you can't dynamically change the handle's value type without recreating all edges .

I wonder: Would it be possible to encode the type information in a custom edge?

So for example a source handle of type string would always use an edge with the data type "string" encoded in the edge data. A target handle of data type string would validate if the incoming edge has the desired type.

south sorrel
shadow meteor
#

@south sorrel Understood. But how can I force a specific handle to always use a certain edge type?

south sorrel
#

You will basically do something like this

  1. Assign handle ids like true,false,result etc for ConditionNode and for the other node with data types it can have handle ids like lets say string, number

  2. On every connection, in the isValidConnection you will check the edges which are of that specific node. If there is no edge of that node connected with handles 2-4, you establish the connection(i.e No check of type).

  3. Now if there are any handles of the node with which the connection is being established, you will check if the edge which is already connected among 2-4 is of lets say type string(check using handle id). If it is of type string, then you will also allow connections between handles 2-4 with type string, otherwise not. etc