I have a React component that retrieves an array of objects (key-value pairs) from a REST API via an HTML endpoint:
[
{
"id": 1,
"grouping1": "first-level-node-1",
"grouping2": "second-level-node-1",
"theThing": "third-level-node-1",
"someData": "data here that is associated with theThing",
"someUrl": "http://someurl.com"
},
{
"id": 2,
"grouping1": "first-level-node-2",
"grouping2": "second-level-node-1",
"theThing": "third-level-node-1",
.
.
.
}
]
I am trying to manipulate the JSON response so that it gets displayed with react-simple-tree-menu. To generate a TreeMenu, data needs to be provided as an array:
// as an array
const treeData = [
{
key: 'first-level-node-1',
label: 'Node 1 at the first level',
..., // any other props you need, e.g. url
nodes: [
{
key: 'second-level-node-1',
label: 'Node 1 at the second level',
nodes: [
{
key: 'third-level-node-1',
label: 'Last node of the branch',
nodes: [] // you can remove the nodes property or leave it as an empty array
},
],
},
],
},
{
key: 'first-level-node-2',
label: 'Node 2 at the first level',
},
];
or as an object:
// or as an object
const treeData = {
'first-level-node-1': { // key
label: 'Node 1 at the first level',
index: 0, // decide the rendering order on the same level
..., // any other props you need, e.g. url
nodes: {
'second-level-node-1': {
label: 'Node 1 at the second level',
index: 0,
nodes: {
'third-level-node-1': {
label: 'Node 1 at the third level',
index: 0,
nodes: {} // you can remove the nodes property or leave it as an empty array
},
},
},
},
},
'first-level-node-2': {
label: 'Node 2 at the first level',
index: 1,
},
};
I have tried categorizing the JSON response based on grouping1 (first level node) and grouping2 (second level node) to make it 'fit' into the treeData:
const fetchItems = async () => {
const data = await fetch('http://localhost:3001/stuff');
const input = await data.json();
const output = input.reduce((acc, item) => ({
...acc,
[item.grouping1]: {
...acc[item.grouping1],
[item.grouping2]: [
...(acc[item.gropuing1] && acc[item.grouping1][item.grouping2] || []),
item,
]
}
}), {})
and now I have objects (grouping1) that contain objects (grouping2) that contain the array of key-value pairs.
first-level-node-1:
second-level-node-1: Array(4)
0: {id: 1, grouping1: "first-level-node-1", grouping2: "second-level-node-1", theThing: "third-level-node-1"}
.
.
.
first-level-node-2:
second-level-node-1: Array(16)
0: {id: 2, grouping1: "first-level-node-2", grouping2: "second-level-node-1", theThing: "third-level-node-1"}
.
.
.
But this isn't the treeData structure that react-simple-tree-menu wants. How can I massage the JSON response to fit the treeData structure?
Here's an excellent write-up for how to get a Sidebar Menu up and running in React, but nothing that shows how to get a typical JSON response to fit into the required structure.
Update:
The following is the TreeMenu data that controls this react-simple-tree-menu component
<TreeMenu
data={[
{
key: 'mammal',
label: 'Mammal',
nodes: [
{
key: 'canidae',
label: 'Canidae',
nodes: [
{
key: 'dog',
label: 'Dog',
nodes: [],
url: 'https://www.google.com/search?q=dog'
},
{
key: 'fox',
label: 'Fox',
nodes: [],
url: 'https://www.google.com/search?q=fox'
},
{
key: 'wolf',
label: 'Wolf',
nodes: [],
url: 'https://www.google.com/search?q=wolf'
}
],
url: 'https://www.google.com/search?q=canidae'
}
],
url: 'https://www.google.com/search?q=mammal'
},
{
key: 'reptile',
label: 'Reptile',
nodes: [
{
key: 'squamata',
label: 'Squamata',
nodes: [
{
key: 'lizard',
label: 'Lizard',
url: 'https://www.google.com/search?q=lizard'
},
{
key: 'snake',
label: 'Snake',
url: 'https://www.google.com/search?q=snake'
},
{
key: 'gekko',
label: 'Gekko',
url: 'https://www.google.com/search?q=gekko'
}
],
url: 'https://www.google.com/search?q=squamata'
}
],
url: 'https://www.google.com/search?q=reptile'
}
]}
debounceTime={125}
disableKeyboard={false}
hasSearch
onClickItem={function noRefCheck(){}}
resetOpenNodesOnDataUpdate={false}
/>
If I'm understanding this correctly, Mammal is first-level-node-1 and Reptile is first-level-node-2. Canidae and Squamata are both second-level-node-1 under their respective first-level-nodes. Dog, Fox and Wolf are third-level-node-1, node-2 and node-3 respectively. Lizard, Snake and Gekko are also third-level-node-1, node-2 and node-3. The example I used at the very top of this post may be confusing things. My apologies if that is the case.
Here is the JSON data that more closely resembles what I'm working with:
[
{
"id": 2,
"grouping1": "I124",
"grouping2": "Cross_Streets",
"theThing": "12th",
"url": "http://url2.com"
},
{
"id": 3,
"grouping1": "I124",
"grouping2": "Cross_Streets",
"theThing": "13th",
"url": "http://url3.com"
},
{
"id": 4,
"grouping1": "I124",
"grouping2": "Cross_Streets",
"theThing": "4th",
"url": "http://url4.com"
},
{
"id": 14,
"grouping1": "I124",
"grouping2": "Ramps",
"theThing": "Ramp_A",
"url": "http://url14.com"
},
{
"id": 15,
"grouping1": "I124",
"grouping2": "Ramps",
"theThing": "Ramp_B",
"url": "http://url15.com"
},
{
"id": 41,
"grouping1": "I75",
"grouping2": "Cross_Streets",
"theThing": "100th",
"url": "http://url41.com"
}
]
The goal is to make the above JSON look like this in react-simple-tree-menu:
+ I124
+ Cross_Streets
12th
13th
4th
+ Ramps
Ramp_A
Ramp_B
+ I75
+ Cross_Streets
4th
Here's a possible solution that you could apply, however, I am not sure where you are getting the labels
from, but I'll leave that up to you.
Here's an example with an object
as the result:
const DATA = [
{
id: 2,
grouping1: "I124",
grouping2: "Cross_Streets",
theThing: "12th",
url: "http://url2.com"
},
{
id: 3,
grouping1: "I124",
grouping2: "Cross_Streets",
theThing: "13th",
url: "http://url3.com"
},
{
id: 4,
grouping1: "I124",
grouping2: "Cross_Streets",
theThing: "4th",
url: "http://url4.com"
},
{
id: 14,
grouping1: "I124",
grouping2: "Ramps",
theThing: "Ramp_A",
url: "http://url14.com"
},
{
id: 15,
grouping1: "I124",
grouping2: "Ramps",
theThing: "Ramp_B",
url: "http://url15.com"
},
{
id: 41,
grouping1: "I75",
grouping2: "Cross_Streets",
theThing: "100th",
url: "http://url41.com"
}
];
const resultAsObject = DATA.reduce((accumulator, item) => {
const groupId = item["grouping1"];
const subGroupId = item["grouping2"];
const subGroupItemId = item["theThing"];
const url = item["url"];
const group = accumulator[groupId] || {
key: groupId.toLowerCase(),
label: groupId,
nodes: []
};
const subGroup = group.nodes.find(
item => item.key === subGroupId.toLowerCase()
) || {
key: subGroupId.toLowerCase(),
label: subGroupId,
nodes: []
};
const updatedSubGroupNodes = [
{
key: subGroupItemId.toLowerCase(),
label: subGroupItemId,
url: url,
nodes: []
}
];
const updatedSubGroup = {
...subGroup,
nodes: updatedSubGroupNodes
};
const t1 = [...group.nodes, updatedSubGroup].reduce((acc, i) => {
const category = acc.find(t => t.key === i.key) || {
key: i.key,
label: i.label,
nodes: []
};
const updatedNodes = [...category.nodes, ...i.nodes];
const updatedCategory = { ...category, nodes: updatedNodes };
// replace the existing category object and append
// the updated object with populated `nodes` property
return [...acc.filter(t => t.key !== category.key), updatedCategory];
}, []);
const updatedGroup = {
...group,
nodes: t1
};
return {
...accumulator,
[groupId]: updatedGroup
};
}, {});
console.log(resultAsObject);