I have an App component:
import { useEffect, useState } from 'react';
import './App.css';
import TreeView from './components/TreeView';
interface DetailedPart {
id: number;
guid: string;
parentName: string;
componentName: string;
partNumber: string;
title: string;
quantity: number;
type: string;
item: string;
material: string;
}
export interface Node {
id: number,
guid: string,
children: Node[],
name: string,
parentName: string;
partNumber: string;
title: string;
quantity: number;
type: string;
item: string;
material: string;
}
function App() {
console.log('App rendered');
const [detparts, setDetparts] = useState<DetailedPart[]>([]);
const [tree, setTree] = useState<Node[]>([]);
useEffect(() => {
console.log('useEffect called');
populateParts();
populateTree();
}, []);
const handleClick = (id: number) => {
console.log("clicked!")
getSubGrid(id);
};
const dataGrid = detparts === undefined
? <p><em>Loading... Please refresh once the ASP.NET backend has started. See <a href="https://aka.ms/jspsintegrationreact">https://aka.ms/jspsintegrationreact</a> for more details.</em></p>
: <table className="table" aria-labelledby="tabelLabel"
>
<thead>
<tr>
<th className='t-head'>PARENTNAME</th>
<th className='t-head'>COMPONENTNAME</th>
<th className='t-head'>PARTNUMBER</th>
<th className='t-head'>TITLE</th>
<th className='t-head'>QUANTITY</th>
<th className='t-head'>TYPE</th>
<th className='t-head'>ITEM</th>
<th className='t-head'>MATERIAL</th>
</tr>
</thead>
<tbody>
{detparts.map(part =>
<tr key={part.guid}>
<td>{part.parentName}</td>
<td>{part.componentName}</td>
<td>{part.partNumber}</td>
<td>{part.title}</td>
<td>{part.quantity}</td>
<td>{part.type}</td>
<td>{part.item}</td>
<td>{part.material}</td>
</tr>
)}
</tbody>
</table>;
return (
<div className="container" style={{ display: 'flex', height: '100vh', backgroundColor: '#555' }}>
<div style={{ width: '33%', overflow: 'auto', backgroundColor: '#1e1e1e', color: 'white' }}>
<TreeView data={tree} handleClick={handleClick} />
</div>
<div style={{ flex: 1, padding: '20px', color: 'white' }}>
<h1 id="tabelLabel">Testing Functionality for Tree and Datagrid</h1>
{dataGrid}
</div>
</div>
);
function populateParts() {
fetch('detparts')
.then(response => response.json())
.then(data => {
try {
console.log('detparts data: ', data);
setDetparts(data);
} catch (e) {
console.error('Error parsing JSON:', e);
}
})
.catch(error => {
console.error('Error fetching parts:', error);
});
};
function populateTree() {
fetch('tree')
.then(response => response.json())
.then(data => {
try {
console.log('poptree data: ', data);
const newNodeArray: Node[] = [];
newNodeArray.push(data);
setTree(newNodeArray);
} catch (error) {
console.error('Error parsing JSON:', error);
}
})
.catch(error => {
console.error('Error fetching tree:', error);
});
};
function getSubGrid(id: number) {
fetch(`subtree/${id}`)
.then(response => response.json())
.then(data => {
try {
const partsArray: Node[] = [];
partsArray.push(data);
const flattenedPartsArray: DetailedPart[] = [];
function flattenNodes(nodes: Node[]) {
for (const node of nodes) {
const { children, name, ...rest } = node;
const flattenedPart = { ...rest, componentName: name };
flattenedPartsArray.push(flattenedPart);
flattenNodes(children);
}
}
flattenNodes(partsArray);
setDetparts(flattenedPartsArray);
} catch (error) {
console.error('Error parsing JSON:', error);
}
})
.catch(error => {
console.error('Error fetching tree:', error);
});
};
}
export default App;
This component is rendering a recursive TreeView component and a table. Here’s the TreeView (and sub component TreeNode):
import { Node } from '../App';
import { useState } from 'react';
const TreeNode = ({ node, handleClick }: { node: Node, handleClick: any }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
const handleClickWithId = () => {
handleClick(node.id);
};
return (
<div onClick={handleClickWithId} className="tree-node" style={{ paddingLeft: '10px', paddingTop: '5px' }}>
{node.children && (
<button onClick={toggle} className="toggle-icon"
style={{ background: 'none', border: 'none', color: '#d4d4d4' }}>
{isOpen ? 'v' : '>'}
</button>)}
<span>{node.name}</span>
{isOpen && <TreeView data={node?.children} handleClick={handleClick} />}
</div>
);
};
const TreeView = ({ data, handleClick }: { data: Node[], handleClick: any } ) => {
return (
<div className="tree-view" >
{Array.isArray(data) && data.map((node: Node, index: number) => (
<TreeNode key={`${node.guid}-${index}`} node={node} handleClick={handleClick} />
))}
</div>
);
};
export default TreeView;
The point of this react app is to populate the table with all child-parts every time one clicks in a Node in the TreeView. However, I noticed the /subtree/{id} request is being repeated as many times as many levels down my nested nodes we go… My Node[] is populated as from a Node object response from the server, which is a nested object, multiple levels deep. What is wrong? Why the repeated requests? Is the onClick handling incorrect? Forgive me in advance for my noobness, I am new to React. Thanks!
2
Answers
Thank you for the event tip! I updated my TreeNode component function to join both onClick calls into one, and I added the event.stopPropagation call:
const toggle = (event: any) => { handleClick(node.id); event.stopPropagation(); setIsOpen(!isOpen); };
after this (and removing the onClick={handleClickWithId} and respective handleClickWithId implementation) the functionality is working perfectlyIn your TreeNode component, you have an onClick handler on the div that wraps the entire component content. This means that any click within this div, including on the button that toggles the node open and closed, will trigger the handleClickWithId function. This could potentially lead to unnecessary re-renders.
One way to address this is to stop the click event from propagating up the DOM tree when the toggle button is clicked. You can do this by modifying the toggle function as follows:
This will prevent the click event on the toggle button from triggering the handleClickWithId function.
Also, ensure that the handleClick function passed down from the App component doesn’t cause a state change that leads to a re-render of the App component unless necessary.
First try with your original TreeView Component and if it doesn’t work then
Try this code for TreeView if it works or I will check again if it’s not.