actions/graphActions:
```
import axios from 'axios';
// import { useDispatch } from 'react-redux';
import { FETCH\_GRAPH\_DATA, FETCH\_GRAPH\_DATA\_LOADING } from './types';

const API\_BASE\_URL = 'http://localhost:5000/graph-data';
// const API\_BASE\_URL = `http://localhost:3001/graph-data`;

export const fetchData = (centralNodeId) => async (dispatch) => {
 try {
 // const storageKey = centralNodeId ? `graphData-${centralNodeId}` : 'graphData';
 
 // // Check if data is available in sessionStorage
 // const cachedData = sessionStorage.getItem(storageKey);
 // if (cachedData) {
 // console.log('Using cached data');
 // return dispatch({
 // type: FETCH\_GRAPH\_DATA,
 // payload: JSON.parse(cachedData),
 // });
 // }
 // dispatch({ type: FETCH\_GRAPH\_DATA\_LOADING }); 
 
 const requestUrl = centralNodeId ? `${API\_BASE\_URL}/${centralNodeId}` : `${API\_BASE\_URL}`;
 const response = await axios.get(requestUrl);
 const { nodes, edges } = response.data;
 
 
 const cyNodes = nodes.map((node) => ({
 data: { ...node, id: node.id, label: node.label },
 }));

 const cyEdges = edges
 .filter((edge) => edge.from && edge.to)
 .map((edge) => ({
 data: { ...edge, id: edge.id, source: edge.from, target: edge.to },
 }));

 const data = { nodes: [...cyNodes], edges: [...cyEdges] };
 
 // Store the fetched data in sessionStorage

 // sessionStorage.setItem(storageKey, JSON.stringify(data));
 console.log('Fetched data from API');fetchData
 console.log('data:', data);

 return dispatch({
 type: FETCH\_GRAPH\_DATA,
 payload: data,
 });
 // return dispatch({
 // type: FETCH\_GRAPH\_DATA,
 // payload: data,
 // });
 } catch (error) {
 console.error('Error fetching data:', error);
 }
};
```

components/VisNetwork.js
```
import { useEffect, useRef, useState } from 'react';
import cytoscape from 'cytoscape';
import { useSelector, useDispatch } from 'react-redux';
// import { connect, useSelector, useDispatch } from 'react-redux';
import { fetchData } from '../actions/graphActions';
import cxtmenu from 'cytoscape-cxtmenu';
import dagre from 'cytoscape-dagre';

cytoscape.use(cxtmenu);
cytoscape.use(dagre);

const VisNetwork = () => {
 const [isCytoscapeInitialized, setIsCytoscapeInitialized] = useState(false);
 const cyRef = useRef(null);
 const dispatch = useDispatch();
 let graph = useSelector((state) => state.graph);
 const { loading } = useSelector((state) => state.graph);
 let [cyInstance, setCyInstance] = useState(null);

 useEffect(() => {
 dispatch(fetchData());
 }, [dispatch, fetchData]);

 useEffect(() => {
 // let cyInstance;

 const initializeCytoscape = async () => {
 if (cyRef.current && graph.nodes && graph.edges) {
 const cy = cytoscape({
 container: cyRef.current,
 elements: {
 nodes: graph.nodes.map((node) => ({ data: { ...node.data } })),
 edges: graph.edges.map((edge) => ({ data: { ...edge.data } })),
 },
 style: [
 {
 selector: 'node',
 style: {
 'background-color': '#666',
 label: 'data(label)',
 },
 },
 {
 selector: 'edge',
 style: {
 width: 3,
 'line-color': '#ccc',
 'target-arrow-color': '#ccc',
 'target-arrow-shape': 'triangle',
 'curve-style': 'bezier',
 },
 },
 ],
 layout: {
 name: 'cose',
 },
 headless: false,
 });

 cy.cxtmenu({
 selector: 'core',
 commands: [
 {
 content: 'Cose Layout',
 select: () => {
 const layout = cy.layout({
 name: 'cose',
 animate: true,
 });
 layout.run();
 },
 },
 {
 content: 'Dagre Layout',
 select: () => {
 const layout = cy.layout({
 name: 'dagre',
 animate: true,
 });
 layout.run();
 },
 },
 ],
 });
 cy.on('tap', 'node', handleNodeTap);
 // cy.on('tap', 'node', async (event) => {
 // var nodeData = event.target.data();
 // var nodeId = '';
 // console.log('Tapped node data:', nodeData);
 // if (!nodeData.data) {
 // nodeId = nodeData.id;
 // } else {
 // nodeId = nodeData.data.id;
 // }
 // console.log('Tapped node id:', nodeId);

 // await dispatch(fetchData(nodeId));

 // // var nodes = graph.nodes;
 // // var edges = graph.edges;
 // console.log('nodes:', graph.nodes);
 // console.log('edges:', graph.edges);
 // cy.json({ elements: graph.nodes.concat(graph.edges) });
 // const layout = cy.layout({ name: 'cose' });
 // layout.run();
 // });
 setCyInstance(cy);

 const layout = cy.layout({ name: 'cose' });
 layout.run();

 cyInstance = cy;
 setIsCytoscapeInitialized(true);
 }
 };

 if (!isCytoscapeInitialized) {
 dispatch(fetchData()).then(() => {
 initializeCytoscape();
 });
 }

 const handleVisibilityChange = () => {
 if (document.hidden) {
 sessionStorage.clear();
 }
 };
 document.addEventListener('visibilitychange', handleVisibilityChange);

 return () => {
 if (cyInstance) {
 cyInstance.destroy();
 }
 document.removeEventListener('visibilitychange', handleVisibilityChange);
 };
 }, [cyRef, dispatch, graph, isCytoscapeInitialized, loading]);

 useEffect(() => {
 if (cyInstance) {
 // Update the Cytoscape instance with new data from the store
 cyInstance.json({ elements: graph.nodes.concat(graph.edges) });

 // Run the layout to update the graph
 const layout = cyInstance.layout({ name: 'cose' });
 layout.run();
 }
 }, [graph, cyInstance]);

 const handleNodeTap = async (event) => {
 if (!cyInstance) return;

 var nodeData = event.target.data();
 var nodeId = '';
 console.log('Tapped node data:', nodeData);
 if (!nodeData.data) {
 nodeId = nodeData.id;
 } else {
 nodeId = nodeData.data.id;
 }
 console.log('Tapped node id:', nodeId);
 
 fetchData(nodeId);
 // await dispatch(fetchData(nodeId));
 
 // const cy = cytoscape({ headless: true });
 // cy.add(graph.nodes.concat(graph.edges));
 // cy.nodes().forEach((node) => {
 // if (node.id() === nodeId) {
 // node.connectedEdges().forEach((edge) => {
 // cy.remove(edge);
 // });
 // }
 // });
 // const updatedElements = cy.elements().jsons();
 // cy.destroy();

 // cyInstance.json({ elements: updatedElements });

 // Remove all edges that are not connected to the tapped node
 // cyInstance.edges().forEach((edge) => {
 // if (edge.source().id() !== nodeId && edge.target().id() !== nodeId) {
 // cyInstance.remove(edge);
 // }
 // });

 // cyInstance.json({ elements: graph.nodes.concat(graph.edges) });
 // // cyRef.current.json({ elements: updatedElements });
 // const layout = cyInstance.layout({ name: 'cose' });
 // layout.run();
 };

 return (
 
 {(!isCytoscapeInitialized || loading) && Loading...

}
 

 );
};

// const mapStateToProps = (state) => ({
// graph: state.graph,
// });

// export default connect(mapStateToProps)(VisNetwork);
export default VisNetwork;
```

reducers/graphReducer.js
```
const initialState = {
 nodes: [],
 edges: [],
 loading: true,
};
 
 const graphReducer = (state = initialState, action) => {
 switch (action.type) {
 case 'FETCH\_GRAPH\_DATA\_LOADING':
 return {
 ...state,
 loading: true,
 };
 case 'FETCH\_GRAPH\_DATA':
 return {
 ...state,
 nodes: action.payload.nodes.map((node) => ({ ...node })),
 edges: action.payload.edges.map((edge) => ({ ...edge })),
 loading: false,
 };
 default:
 return state;
 }
 };
 
 export default graphReducer;
 ```

I'm unable to solve "Error fetching data: Error: Invariant failed: A state mutation was detected between dispatches, in the path 'graph.edges.0.data.id'." on clicking any of the nodes. Also the nodes data doesn't update
ctions/graphActions:
```
'axios'에서 axios를 임포트합니다;
// 'react-redux'에서 { useDispatch }를 가져옵니다;
'./types'에서 { FETCH\_GRAPH\_DATA, FETCH\_GRAPH\_DATA\_LOADING }를 임포트합니다;

const API\_BASE\_URL = 'http://localhost:5000/graph-data';
// const API\_BASE\_URL = `http://localhost:3001/graph-data`;

export const fetchData = (centralNodeId) => async (dispatch) => {
 try {
 // const storageKey = centralNodeId ? `graphData-${centralNodeId}` : 'graphData';
 
 // // 세션스토리지에서 데이터를 사용할 수 있는지 확인합니다.
 // const cachedData = sessionStorage.getItem(storageKey);
 // if (cachedData) {
 // console.log('캐시된 데이터 사용 중');
 // return dispatch({
 // type: FETCH\_GRAPH\_DATA,
 // 페이로드: JSON.parse(cachedData),
 // });
 // }
 // dispatch({ type: FETCH\_GRAPH\_DATA\_LOADING }); 
 
 const requestUrl = centralNodeId ? `${API\_BASE\_URL}/${centralNodeId}` : `${API\_BASE\_URL}`;
 const response = await axios.get(requestUrl);
 const { 노드, 에지 } = response.data;
 
 
 const cyNodes = nodes.map((node) => ({ {
 data: { ...node, id: node.id, label: node.label },
 }));

 const cyEdges = edges
 .filter((edge) => edge.from && edge.to)
 .map((edge) => ({
 data: { ...edge, id: edge.id, source: edge.from, target: edge.to },
 }));

 const data = { nodes: [...cyNodes], edges: [...cyEdges] };
 
 // 가져온 데이터를 세션 스토리지에 저장합니다.

 // sessionStorage.setItem(storageKey, JSON.stringify(data));
 console.log('API에서 데이터를 가져왔습니다');fetchData
 console.log('data:', data);

 return dispatch({
 type: FETCH\_GRAPH\_DATA,
 페이로드: 데이터,
 });
 // return dispatch({
 // type: FETCH\_GRAPH\_DATA,
 // 페이로드: 데이터,
 // });
 } catch (error) {
 console.error('데이터 불러오는 중 오류 발생:', error);
 }
};
```

components/VisNetwork.js
```
'react'에서 { useEffect, useRef, useState }를 임포트합니다;
'cytoscape'에서 cytoscape를 임포트합니다;
'react-redux'에서 { useSelector, useDispatch }를 임포트합니다;
// 'react-redux'에서 { connect, useSelector, useDispatch }를 임포트합니다;
'../actions/graphActions'에서 { fetchData }를 임포트합니다;
'cytoscape-cxtmenu'에서 cxtmenu를 임포트합니다;
'cytoscape-dagre'에서 dagre를 임포트합니다;

cytoscape.use(cxtmenu);
cytoscape.use(dagre);

const VisNetwork = () => {
 const [isCytoscapeInitialized, setIsCytoscapeInitialized] = useState(false);
 const cyRef = useRef(null);
 const dispatch = useDispatch();
 let graph = useSelector((state) => state.graph);
 const { loading } = useSelector((state) => state.graph);
 let [cyInstance, setCyInstance] = useState(null);

 useEffect(() => {
 dispatch(fetchData());
 }, [dispatch, fetchData]);

 useEffect(() => {
 // let cyInstance;

 const initializeCytoscape = async () => {
 if (cyRef.current && graph.nodes && graph.edges) {
 const cy = cytoscape({
 container: cyRef.current,
 elements: {
 nodes: graph.nodes.map((node) => ({ data: { ...node.data } })),
 edges: graph.edges.map((edge) => ({ data: { ...edge.data } })),
 },
 style: [
 {
 selector: '노드',
 style: {
 'background-color': '#666',
 레이블: '데이터(레이블)',
 },
 },
 {
 selector: 'edge',
 style: {
 width: 3,
 '선-색': '#ccc',
 'target-arrow-color': '#ccc',
 'target-arrow-shape': '삼각형',
 '곡선 스타일': '베지어',
 },
 },
 ],
 레이아웃: {
 name: 'cose',
 },
 headless: false,
 });

 cy.cxtmenu({
 selector: 'core',
 commands: [
 {
 content: '코즈 레이아웃',
 select: () => {
 const layout = cy.layout({
 name: 'cose',
 animate: true,
 });
 layout.run();
 },
 },
 {
 content: '다그레 레이아웃',
 select: () => {
 const layout = cy.layout({
 name: 'dagre',
 animate: true,
 });
 layout.run();
 },
 },
 ],
 });
 cy.on('탭', '노드', 핸들노드탭);
 // cy.on('tap', 'node', async (event) => {
 // var nodeData = event.target.data();
 // var nodeId = '';
 // console.log('탭한 노드 데이터:', nodeData);
 // if (!nodeData.data) {
 // nodeId = nodeData.id;
 // } else {
 // nodeId = nodeData.data.id;
 // }
 // console.log('탭한 노드 아이디:', nodeId);

 // await dispatch(fetchData(nodeId));

 // // var nodes = graph.nodes;
 // // var edges = graph.edges;
 // console.log('nodes:', graph.nodes);
 // console.log('edges:', graph.edges);
 // cy.json({ elements: graph.nodes.concat(graph.edges) });
 // const layout = cy.layout({ name: 'cose' });
 // layout.run();
 // });
 setCyInstance(cy);

 const layout = cy.layout({ name: 'cose' });
 layout.run();

 cyInstance = cy;
 setIsCytoscapeInitialized(true);
 }
 };

 if (!isCytoscapeInitialized) {
 dispatch(fetchData()).then(() => {
 initializeCytoscape();
 });
 }

 const handleVisibilityChange = () => {
 if (document.hidden) {
 sessionStorage.clear();
 }
 };
 document.addEventListener('visibilitychange', handleVisibilityChange);

 return () => {
 if (cyInstance) {
 cyInstance.destroy();
 }
 document.removeEventListener('visibilitychange', handleVisibilityChange);
 };
 }, [cyRef, dispatch, graph, isCytoscapeInitialized, loading]));

 useEffect(() => {
 if (cyInstance) {
 // 저장소의 새 데이터로 사이토스케이프 인스턴스를 업데이트합니다.
 cyInstance.json({ elements: graph.nodes.concat(graph.edges) });

 // 레이아웃을 실행하여 그래프를 업데이트합니다.
 const layout = cyIn