試作内容
<Child
  Elements={el}
  Styles={sy}
/>上記のように、props で cytoscape へ設定するデータを渡して更新する Child コンポーネントの作成を目指します。
テンプレ作成
ベースとなるアプリは以下のコマンドで用意しました。
npx create-react-app my-app --template=typescript
cd my-app
yarn add cytoscape
yarn add -D @types/cytoscape試作したコンポーネント
import React, { FC, useRef, useEffect } from 'react';
import cytoscape, { Core, ElementDefinition, Stylesheet } from 'cytoscape';
const Child: FC<{
  Elements?: ElementDefinition[],
  Styles?: Stylesheet[],
}> = ({
  Elements = undefined,
  Styles = undefined,
}) => {
    const refEl = useRef(null);
    const refCy = useRef<Core | null>(null);
    useEffect(() => {
      const container = refEl.current! as HTMLDivElement;
      refCy.current = cytoscape({
        container: container,
        elements: Elements,
        style: Styles,
      });
      const cy = refCy.current;
      return (() => {
        cy.destroy();
      })
    }, []);
    useEffect(() => {
      const cy = refCy.current;
      cy?.json({ elements: Elements, styles: Styles });
    }, [Elements, Styles]);
    return (
      <div ref={refEl} style={{ width: '300px', height: '200px' }}></div>
    );
  }
export default Child;主なポイントは、useEffect を以下の2つに分けて処理するところでしょうか。
- 初期に描画する処理(14行目以降)
- 更新された props のデータで描画する処理(27行目以降)
14行目の useEffect の第2引数に Elements と Styles を入れても更新は可能でしたが、はじめからの描画になるので、図の変更(ノードの移動など)が戻ってしまいました。
その為、27行目の useEffect で props の更新に反応させ、
src/Child.tsx
  Line 25:8:  React Hook useEffect has missing dependencies: 'Elements' and 'Styles'. 
 Either include them or remove the dependency array  react-hooks/exhaustive-depsと言う指摘は無視しています。
やはりこの手のライブラリ使用は useEffect でハマりそうな気がしますね。
あと、cytoscape の要素の初期の高さをどうするか悩みそうでした。
何もしないと高さがゼロになって何も描かれないんですよね。
仕方がないので今回は固定値を設定しました。
作成したコンポーネントを使用したスクリーンショット
ボタンクリックで、Node1 の色が変わります。
- blank:黒
- A:赤
- B:青



その他のファイル
Child.tsx の他に、修正または追加したファイルは以下の通りです。
import React, { useState } from 'react';
import Child from './Child';
import Elements from './cy-element';
import Styles from './cy-style';
function App() {
  const [el, setElements] = useState(Elements);
  const [sy, setStyles] = useState(Styles);
  const onClick = (type: string) => {
    const node = el.find(e => e.data.id === "n01");
    node!.data.type = type;
    setElements([...el]);
  };
  return (
    <div>
      <Child
        Elements={el}
        Styles={sy}
      />
      <button onClick={() => onClick('')}>blank</button>
      <button onClick={() => onClick('A')}>A</button>
      <button onClick={() => onClick('B')}>B</button>
    </div>
  );
}
export default App;import { ElementDefinition } from 'cytoscape';
export default [{
  "data": {
    "id": "n01",
    "label": "Node1",
    "type": "A"
  }
}, {
  "data": {
    "id": "n02",
    "label": "Node2"
  }
}, {
  "data": {
    "source": "n01",
    "target": "n02"
  }
}] as ElementDefinition[];import { Stylesheet } from 'cytoscape';
export default
[
  {
    "selector": "node",
    "style": {
      'label': 'data(label)',
      "background-color": "black"
    }
  },
  {
    "selector": "node[type='A']",
    "style": {
      "background-color": "red"
    }
  },
  {
    "selector": "node[type='B']",
    "style": {
      "background-color": "blue"
    }
  }
] as Stylesheet[]; 
 


コメント