0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactflow ExamplesのComputing Flowsがうまく動かない

Last updated at Posted at 2024-09-27

Reactflowとは?

Reactflowは、Reactを使用してインタラクティブなフローチャートやダイアグラムを作成するためのライブラリです。公式サイトには、さまざまな例が提供されており、その中には「Computing Flows」というプログラムも含まれています。

Computing Flowsの例では、useNodesDatauseHandleConnectionsupdateNodeといったヘルパーを使用してフローを計算する方法が示されています。詳細な情報は、公式のComputing Flows Guideで確認できます。

ローカルでの問題

しかし、この例をローカルで実行しようとすると、期待通りに動作しないことがあります。私の場合、TextNodeに入力ができないという問題が発生しました。具体的には、updateNodeDataが期待通りに動作しませんでした。

image.png

私はJavaScriptの代わりにNext.jsを使用していましたが、いろいろ試しても問題を解決できませんでした。

問題の原因と解決策

問題の原因は、オリジナルのコードに欠けていたたった1つのタグにありました。page.tsx<ReactFlow>コンポーネントを<ReactFlowProvider>でラップする必要があるのです。これにより、ノード内でuseReactFlow(ReactFlowのインスタンス)を使用できるようになり、データが正しく更新されるようになります。

image.png

Next.jsに変換されたオリジナルコード

以下は、Next.jsに変換されたオリジナルのコードです。page.tsx<ReactFlowProvider>でラップしたものとなっています。

page.tsx

'use client';

import { useCallback } from 'react';
import {
  ReactFlow,
  Controls,
  addEdge,
  Connection,
  useNodesState,
  useEdgesState,
  Background,
  Edge,
  ReactFlowProvider
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';

import TextNode from './TextNode';
import ResultNode from './ResultNode';
import UppercaseNode from './UppercaseNode';
import { MyNode } from './utils';

const nodeTypes = {
  text: TextNode,
  result: ResultNode,
  uppercase: UppercaseNode,
};

const initNodes: MyNode[] = [
  {
    id: '1',
    type: 'text',
    data: {
      text: 'hello',
    },
    position: { x: -100, y: -50 },
  },
  {
    id: '2',
    type: 'text',
    data: {
      text: 'world',
    },
    position: { x: 0, y: 100 },
  },
  {
    id: '3',
    type: 'uppercase',
    data: { text: '' },
    position: { x: 100, y: -100 },
  },
  {
    id: '4',
    type: 'result',
    data: {},
    position: { x: 300, y: -75 },
  },
];

const initEdges: Edge[] = [
  {
    id: 'e1-3',
    source: '1',
    target: '3',
  },
  {
    id: 'e3-4',
    source: '3',
    target: '4',
  },
  {
    id: 'e2-4',
    source: '2',
    target: '4',
  },
];

const CustomNodeFlow = () => {
  const [nodes, , onNodesChange] = useNodesState(initNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);

  const onConnect = useCallback(
    (connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
    [setEdges],
  );

  return (
    <div className="w-full h-full">
      <ReactFlowProvider>
        <ReactFlow
          nodes={nodes}
          edges={edges}
          onNodesChange={onNodesChange}
          onEdgesChange={onEdgesChange}
          onConnect={onConnect}
          nodeTypes={nodeTypes}
          fitView
        >
          <Controls />
          <Background />
        </ReactFlow>
      </ReactFlowProvider>
    </div>
  );
};

export default CustomNodeFlow;

TextNode.tsx

import { memo } from 'react';
import { Position, NodeProps, Handle, useReactFlow, Node } from '@xyflow/react';

const TextNode = ({ id, data }: NodeProps<Node<{ text: string }>>) => {
  const { updateNodeData } = useReactFlow();
  const reactflw = useReactFlow();

  return (
    <div
      style={{
        background: '#eee',
        color: '#222',
        padding: 10,
        fontSize: 12,
        borderRadius: 10,
      }}
    >
      <div>node {id}</div>
      <div style={{ marginTop: 5 }}>
        <input
          value={data.text}
          onChange={(evt) => {
            updateNodeData(id, { text: evt.target.value });
          }}
          style={{ display: 'block' }}
        />
      </div>
      <Handle type="source" position={Position.Right} />
    </div>
  );
}

export default memo(TextNode);

UppercaseNode.tsx

import { memo, useEffect } from 'react';
import {
  Position,
  NodeProps,
  useReactFlow,
  Handle,
  useHandleConnections,
  useNodesData,
} from '@xyflow/react';

import { isTextNode, type MyNode } from './utils';

const UppercaseNode = ({ id }: NodeProps) => {
  const { updateNodeData } = useReactFlow();
  const connections = useHandleConnections({
    type: 'target',
  });
  const nodesData = useNodesData<MyNode>(connections[0]?.source);
  const textNode = isTextNode(nodesData) ? nodesData : null;

  useEffect(() => {
    updateNodeData(id, { text: textNode?.data.text.toUpperCase() });
  }, [textNode]);

  return (
    <div
      style={{
        background: '#eee',
        color: '#222',
        padding: 10,
        fontSize: 12,
        borderRadius: 10,
      }}
    >
      <Handle
        type="target"
        position={Position.Left}
        isConnectable={connections.length <= 1}
      />
      <div>uppercase transform</div>
      <Handle type="source" position={Position.Right} />
    </div>
  );
}

export default memo(UppercaseNode);

ResultNode.tsx

import { memo } from 'react';
import {
  Handle,
  Position,
  useHandleConnections,
  useNodesData,
  useReactFlow,
} from '@xyflow/react';
import { isTextNode, type MyNode } from './utils';

const ResultNode = () => {
  const connections = useHandleConnections({
    type: 'target',
  });
  const nodesData = useNodesData<MyNode>(
    connections.map((connection) => connection.source),
  );
  const textNodes = nodesData.filter(isTextNode);
  const reactFlow = useReactFlow();

  return (
    <div
      style={{
        background: '#eee',
        color: '#222',
        padding: 10,
        fontSize: 12,
        borderRadius: 10,
      }}
    >
      <Handle type="target" position={Position.Left} />
      <div>
        incoming texts:{' '}
        {textNodes.map(({ data }, i) => <div key={i}>{data.text}</div>) ||
          'none'}
      </div>
    </div>
  );
}

export default memo(ResultNode);

utils.ts

import { Node } from '@xyflow/react';

export type TextNode = Node<{ text: string }, 'text'>;
export type ResultNode = Node<{}, 'result'>;
export type UppercaseNode = Node<{ text: string }, 'uppercase'>;
export type MyNode = TextNode | ResultNode | UppercaseNode;

export const isTextNode = (node: any): node is TextNode | UppercaseNode => {
  return node.type === 'text' || node.type === 'uppercase';
};

まとめ

Reactflowを使用してフローを計算する際には、<ReactFlowProvider><ReactFlow>をラップすることが重要です。これにより、ノード内でuseReactFlowを正しく使用でき、データの更新が期待通りに行われるようになります。Next.jsでの実装においても、この点を忘れずに対応することで、問題を解決することができます。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?