diff --git a/examples/react/src/examples/UseNodesData/ResultNode.tsx b/examples/react/src/examples/UseNodesData/ResultNode.tsx index ac9777dd..006af8b7 100644 --- a/examples/react/src/examples/UseNodesData/ResultNode.tsx +++ b/examples/react/src/examples/UseNodesData/ResultNode.tsx @@ -1,20 +1,20 @@ import { memo } from 'react'; import { Handle, Position, useHandleConnections, useNodesData } from '@xyflow/react'; +import { TextNode, isTextNode, type MyNode } from '.'; function ResultNode() { const connections = useHandleConnections({ type: 'target', }); - const nodesData = useNodesData(connections.map((connection) => connection.source)); + const nodesData = useNodesData(connections.map((connection) => connection.source)); + const textNodes = nodesData.filter(isTextNode) as TextNode[]; + + textNodes[0].data; return (
-
- incoming texts:{' '} - {nodesData?.filter((nodeData) => nodeData.text !== undefined).map(({ text }, i) =>
{text}
) || - 'none'} -
+
incoming texts: {textNodes.map(({ data }, i) =>
{data.text}
) || 'none'}
); } diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx index 699dc37e..70c6079d 100644 --- a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -1,16 +1,18 @@ import { memo, useEffect } from 'react'; import { Position, NodeProps, useReactFlow, Handle, useHandleConnections, useNodesData } from '@xyflow/react'; +import { isTextNode, type TextNode, type MyNode } from '.'; function UppercaseNode({ id }: NodeProps) { const { updateNodeData } = useReactFlow(); const connections = useHandleConnections({ type: 'target', }); - const nodeData = useNodesData(connections[0]?.source); + const nodesData = useNodesData(connections[0]?.source); + const textNode = isTextNode(nodesData) ? nodesData : null; useEffect(() => { - updateNodeData(id, { text: nodeData?.text.toUpperCase() }); - }, [nodeData]); + updateNodeData(id, { text: textNode?.data.text.toUpperCase() }); + }, [textNode]); return (
diff --git a/examples/react/src/examples/UseNodesData/index.tsx b/examples/react/src/examples/UseNodesData/index.tsx index 41c537ab..dc0b43dd 100644 --- a/examples/react/src/examples/UseNodesData/index.tsx +++ b/examples/react/src/examples/UseNodesData/index.tsx @@ -17,9 +17,13 @@ import UppercaseNode from './UppercaseNode'; export type TextNode = Node<{ text: string }, 'text'>; export type ResultNode = Node<{}, 'result'>; -export type UppercaseNode = Node<{}, 'uppercase'>; +export type UppercaseNode = Node<{ text: string }, 'uppercase'>; export type MyNode = TextNode | ResultNode | UppercaseNode; +export function isTextNode(node: any): node is TextNode | UppercaseNode { + return node.type === 'text' || node.type === 'uppercase'; +} + const nodeTypes = { text: TextNode, result: ResultNode, @@ -38,7 +42,7 @@ const initNodes: MyNode[] = [ { id: '1a', type: 'uppercase', - data: {}, + data: { text: '' }, position: { x: 100, y: 0 }, }, { diff --git a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte index 7b2d2204..a92acbe4 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/+page.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/+page.svelte @@ -1,3 +1,15 @@ + +
incoming texts:
- {#each $nodeData as data} -
{data.text}
+ {#each textNodes as textNode} +
{textNode.data.text}
{/each}
diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte index 2dcf3bde..be15ac9a 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -7,6 +7,7 @@ useSvelteFlow, type NodeProps } from '@xyflow/svelte'; + import { isTextNode, type MyNode } from './+page.svelte'; type $$Props = NodeProps; @@ -18,10 +19,11 @@ type: 'target' }); - $: nodeData = useNodesData($connections[0]?.source); + $: nodeData = useNodesData($connections[0]?.source); + $: textNode = isTextNode($nodeData) ? $nodeData : null; $: { - updateNodeData(id, { text: $nodeData?.text?.toUpperCase() || '' }); + updateNodeData(id, { text: textNode?.data.text.toUpperCase() || '' }); } diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts index 10480caa..6d9586ed 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -4,40 +4,55 @@ import { shallow } from 'zustand/shallow'; import { useStore } from '../hooks/useStore'; import type { Node } from '../types'; +export interface NodeDataReturn { + id: string; + type: NodeType['type']; + data: NodeType['data']; +} + /** * Hook for receiving data of one or multiple nodes * * @public * @param nodeId - The id (or ids) of the node to get the data from * @param guard - Optional guard function to narrow down the node type - * @returns An array od data objects + * @returns An object (or array of object) with {id, type, data} representing each node */ -export function useNodesData(nodeId: string): NodeType['data'] | null; -export function useNodesData(nodeIds: string[]): NodeType['data'][]; export function useNodesData( - nodeIds: string[], - guard: (node: Node) => node is NodeType -): NodeType['data'][]; + nodeId: string +): { + id: string; + type: NodeType['type']; + data: NodeType['data']; +} | null; +export function useNodesData( + nodeIds: string[] +): { + id: string; + type: NodeType['type']; + data: NodeType['data']; +}[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const nodesData = useStore( useCallback( (s) => { - if (!Array.isArray(nodeIds)) { - return s.nodeLookup.get(nodeIds)?.data || null; - } - const data = []; + const isArrayOfIds = Array.isArray(nodeIds); + const _nodeIds = isArrayOfIds ? nodeIds : [nodeIds]; - for (const nodeId of nodeIds) { - const nodeData = s.nodeLookup.get(nodeId)?.data; - - if (nodeData) { - data.push(nodeData); + for (const nodeId of _nodeIds) { + const node = s.nodeLookup.get(nodeId); + if (node) { + data.push({ + id: node.id, + type: node.type, + data: node.data, + }); } } - return data; + return isArrayOfIds ? data : data[0] ?? null; }, [nodeIds] ), diff --git a/packages/svelte/src/lib/hooks/useNodesData.ts b/packages/svelte/src/lib/hooks/useNodesData.ts index d45c8c9e..1ec53f87 100644 --- a/packages/svelte/src/lib/hooks/useNodesData.ts +++ b/packages/svelte/src/lib/hooks/useNodesData.ts @@ -3,17 +3,13 @@ import { derived, type Readable } from 'svelte/store'; import type { Node } from '$lib/types'; import { useStore } from '$lib/store'; -function areNodesDataEqual(a: (Node['data'] | null)[] | null, b: (Node['data'] | null)[] | null) { - if ((!a && !b) || (!a?.length && !b?.length)) { - true; - } - - if (!a || !b || a.length !== b.length) { +function areNodesDataEqual(a: any[], b: any[]) { + if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) { + if (a[i].data !== b[i].data) { return false; } } @@ -31,42 +27,45 @@ function areNodesDataEqual(a: (Node['data'] | null)[] | null, b: (Node['data'] | */ export function useNodesData( nodeId: string -): Readable; +): Readable<{ + id: string; + type: NodeType['type']; + data: NodeType['data']; +} | null>; export function useNodesData( nodeIds: string[] -): Readable; -export function useNodesData( - nodeIds: string[], - guard: (node: Node) => node is NodeType -): Readable; +): Readable< + { + id: string; + type: NodeType['type']; + data: NodeType['data']; + }[] +>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const { nodes, nodeLookup } = useStore(); - let prevNodesData: (Node['data'] | null)[] | null = null; + let prevNodesData: any = []; return derived([nodes, nodeLookup], ([, nodeLookup], set) => { - let nextNodesData: (Node['data'] | null)[] | null = null; - const nodeIdArray = Array.isArray(nodeIds); + let nextNodesData = []; - if (!nodeIdArray) { - nextNodesData = [nodeLookup.get(nodeIds)?.data || null]; - } else { - const data = []; + const isArrayOfIds = Array.isArray(nodeIds); + const _nodeIds = isArrayOfIds ? nodeIds : [nodeIds]; - for (const nodeId of nodeIds) { - const nodeData = nodeLookup.get(nodeId)?.data; - - if (nodeData) { - data.push(nodeData); - } + for (const nodeId of _nodeIds) { + const node = nodeLookup.get(nodeId); + if (node) { + nextNodesData.push({ + id: node.id, + type: node.type, + data: node.data + }); } - - nextNodesData = data; } if (!areNodesDataEqual(nextNodesData, prevNodesData)) { prevNodesData = nextNodesData; - set(nodeIdArray ? nextNodesData : nextNodesData[0]); + set(isArrayOfIds ? nextNodesData : nextNodesData[0] ?? null); } }); }