From ce6c869df40b2b013484808c742ca508da4a591f Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 10 Feb 2026 10:58:51 +0100 Subject: [PATCH] Improve return type of useNodesdata --- .changeset/old-laws-melt.md | 8 ++++++++ examples/react/src/examples/UseNodesData/TextNode.tsx | 3 ++- .../react/src/examples/UseNodesData/UppercaseNode.tsx | 10 +++++----- .../routes/examples/usenodesdata/UppercaseNode.svelte | 11 +++++------ packages/react/src/hooks/useNodesData.ts | 6 +++--- packages/svelte/src/lib/hooks/useNodesData.svelte.ts | 6 +++--- packages/system/src/types/utils.ts | 7 +++++++ 7 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 .changeset/old-laws-melt.md diff --git a/.changeset/old-laws-melt.md b/.changeset/old-laws-melt.md new file mode 100644 index 0000000000..9025e00d0f --- /dev/null +++ b/.changeset/old-laws-melt.md @@ -0,0 +1,8 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +'@xyflow/system': patch +--- + +Improve return type of useNodesData. Now you can narrow down the data type by checking the node type. + diff --git a/examples/react/src/examples/UseNodesData/TextNode.tsx b/examples/react/src/examples/UseNodesData/TextNode.tsx index dd9f25d1a6..4d66f33ce3 100644 --- a/examples/react/src/examples/UseNodesData/TextNode.tsx +++ b/examples/react/src/examples/UseNodesData/TextNode.tsx @@ -1,7 +1,8 @@ import { memo, useState } from 'react'; import { Position, NodeProps, Handle, useReactFlow } from '@xyflow/react'; +import type { TextNode } from '.'; -function TextNode({ id, data }: NodeProps) { +function TextNode({ id, data }: NodeProps) { const { updateNodeData } = useReactFlow(); const [text, setText] = useState(data.text); const updateText = (text: string) => { diff --git a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx index 792b7c55e0..5b0a64cd69 100644 --- a/examples/react/src/examples/UseNodesData/UppercaseNode.tsx +++ b/examples/react/src/examples/UseNodesData/UppercaseNode.tsx @@ -1,18 +1,18 @@ import { memo, useEffect } from 'react'; import { Position, NodeProps, useReactFlow, Handle, useNodeConnections, useNodesData } from '@xyflow/react'; -import { isTextNode, type TextNode, type MyNode } from '.'; +import { isTextNode, type TextNode, type MyNode, type UppercaseNode } from '.'; -function UppercaseNode({ id }: NodeProps) { +function UppercaseNode({ id }: NodeProps) { const { updateNodeData } = useReactFlow(); const connections = useNodeConnections({ handleType: 'target', }); const nodesData = useNodesData(connections[0]?.source); - const textNode = isTextNode(nodesData) ? nodesData : null; useEffect(() => { - updateNodeData(id, { text: textNode?.data.text.toUpperCase() }); - }, [textNode]); + const text = nodesData?.type === 'text' ? nodesData?.data.text.toUpperCase() : undefined; + updateNodeData(id, { text }); + }, [nodesData]); return (
diff --git a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte index 33990b6d34..3720975779 100644 --- a/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte +++ b/examples/svelte/src/routes/examples/usenodesdata/UppercaseNode.svelte @@ -8,22 +8,21 @@ type Node, type NodeProps } from '@xyflow/svelte'; - import { isTextNode, type MyNode } from './+page.svelte'; + import { type MyNode } from './+page.svelte'; let { id }: NodeProps> = $props(); const { updateNodeData } = useSvelteFlow(); const connections = useNodeConnections({ - id: id, handleType: 'target' }); - let nodeData = $derived(useNodesData(connections.current[0]?.source)); - let textNodeData = $derived(isTextNode(nodeData.current) ? nodeData.current.data.text : null); + let nodesData = $derived(useNodesData(connections.current[0]?.source)); $effect.pre(() => { - const input = textNodeData?.toUpperCase() ?? ''; - updateNodeData(id, { text: input }); + const text = + nodesData.current?.type === 'text' ? nodesData.current?.data.text.toUpperCase() : undefined; + updateNodeData(id, { text }); }); diff --git a/packages/react/src/hooks/useNodesData.ts b/packages/react/src/hooks/useNodesData.ts index 6503f405ca..d639ca2fcf 100644 --- a/packages/react/src/hooks/useNodesData.ts +++ b/packages/react/src/hooks/useNodesData.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { shallowNodeData } from '@xyflow/system'; +import { DistributivePick, shallowNodeData } from '@xyflow/system'; import { useStore } from '../hooks/useStore'; import type { Node } from '../types'; @@ -25,11 +25,11 @@ import type { Node } from '../types'; export function useNodesData( /** The id of the node to get the data from. */ nodeId: string -): Pick | null; +): DistributivePick | null; export function useNodesData( /** The ids of the nodes to get the data from. */ nodeIds: string[] -): Pick[]; +): DistributivePick[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const nodesData = useStore( diff --git a/packages/svelte/src/lib/hooks/useNodesData.svelte.ts b/packages/svelte/src/lib/hooks/useNodesData.svelte.ts index 71432ceb57..58f5eb1ae2 100644 --- a/packages/svelte/src/lib/hooks/useNodesData.svelte.ts +++ b/packages/svelte/src/lib/hooks/useNodesData.svelte.ts @@ -1,4 +1,4 @@ -import { shallowNodeData } from '@xyflow/system'; +import { shallowNodeData, type DistributivePick } from '@xyflow/system'; import type { Node } from '$lib/types'; import { useStore } from '$lib/store'; @@ -12,10 +12,10 @@ import { useStore } from '$lib/store'; */ export function useNodesData( nodeId: string -): { current: Pick | null }; +): { current: DistributivePick | null }; export function useNodesData( nodeIds: string[] -): { current: Pick[] }; +): { current: DistributivePick[] }; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function useNodesData(nodeIds: any): any { const { nodes, nodeLookup } = $derived(useStore()); diff --git a/packages/system/src/types/utils.ts b/packages/system/src/types/utils.ts index 274bfe5597..afea29e131 100644 --- a/packages/system/src/types/utils.ts +++ b/packages/system/src/types/utils.ts @@ -56,3 +56,10 @@ export type Transform = [number, number, number]; * to represent an unbounded extent. */ export type CoordinateExtent = [[number, number], [number, number]]; + +/** + * Using Pick with a union type (e.g. `NodeType`) will merge every property type along all union members. + * See https://github.com/microsoft/TypeScript/issues/28339#issuecomment-463577347 + * Note: Currently you are able to Pick properties that are not in the type without error. + */ +export type DistributivePick = T extends unknown ? { [P in Extract]: T[P] } : never;