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

# 

## Reactflowとは？

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

[Computing Flowsの例](https://reactflow.dev/examples/interaction/computing-flows)では、`useNodesData`、`useHandleConnections`、`updateNode`といったヘルパーを使用してフローを計算する方法が示されています。詳細な情報は、公式の[Computing Flows Guide](https://reactflow.dev/learn/advanced-use/computing-flows)で確認できます。

## ローカルでの問題

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806972/79e38dba-d80b-6b52-e994-f9423ba9369f.png)


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

## 問題の原因と解決策

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806972/ffc51a44-fd99-4603-3063-23dd8a964f4e.png)


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

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

### page.tsx

```typescript
'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

```typescript
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

```typescript
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

```typescript
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

```typescript
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での実装においても、この点を忘れずに対応することで、問題を解決することができます。