Improve return type of useNodesdata

This commit is contained in:
peterkogo
2026-02-10 10:58:51 +01:00
parent be40c34010
commit ce6c869df4
7 changed files with 33 additions and 18 deletions

View File

@@ -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.

View File

@@ -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<TextNode>) {
const { updateNodeData } = useReactFlow();
const [text, setText] = useState(data.text);
const updateText = (text: string) => {

View File

@@ -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<UppercaseNode>) {
const { updateNodeData } = useReactFlow();
const connections = useNodeConnections({
handleType: 'target',
});
const nodesData = useNodesData<MyNode>(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 (
<div style={{ background: '#eee', color: '#222', padding: 10, fontSize: 12, borderRadius: 10 }}>

View File

@@ -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<Node<{ text: string }>> = $props();
const { updateNodeData } = useSvelteFlow();
const connections = useNodeConnections({
id: id,
handleType: 'target'
});
let nodeData = $derived(useNodesData<MyNode>(connections.current[0]?.source));
let textNodeData = $derived(isTextNode(nodeData.current) ? nodeData.current.data.text : null);
let nodesData = $derived(useNodesData<MyNode>(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 });
});
</script>

View File

@@ -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<NodeType extends Node = Node>(
/** The id of the node to get the data from. */
nodeId: string
): Pick<NodeType, 'id' | 'type' | 'data'> | null;
): DistributivePick<NodeType, 'id' | 'type' | 'data'> | null;
export function useNodesData<NodeType extends Node = Node>(
/** The ids of the nodes to get the data from. */
nodeIds: string[]
): Pick<NodeType, 'id' | 'type' | 'data'>[];
): DistributivePick<NodeType, 'id' | 'type' | 'data'>[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useNodesData(nodeIds: any): any {
const nodesData = useStore(

View File

@@ -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<NodeType extends Node = Node>(
nodeId: string
): { current: Pick<NodeType, 'id' | 'data' | 'type'> | null };
): { current: DistributivePick<NodeType, 'id' | 'data' | 'type'> | null };
export function useNodesData<NodeType extends Node = Node>(
nodeIds: string[]
): { current: Pick<NodeType, 'id' | 'data' | 'type'>[] };
): { current: DistributivePick<NodeType, 'id' | 'data' | 'type'>[] };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useNodesData(nodeIds: any): any {
const { nodes, nodeLookup } = $derived(useStore());

View File

@@ -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, K extends string> = T extends unknown ? { [P in Extract<keyof T, K>]: T[P] } : never;