I've been trying to create a generic function which could flatten an array of objects but am failing at every turn. JS isn't my home language. Does anyone know of any existing function which could accept an array of nested objects and output a flattened one?
Input:
const arr = [
{path:'/foo', component: SomeComponent, children: [
{path:'/one', component: SomeComponent},
{path:'/two', component: SomeComponent},
{path:'/three', component: SomeComponent},
]},
{path: '/bar', component: SomeComponent}
]
Expected output:
const flattened_arr = [
{path:'/foo', component: SomeComponent},
{path:'/foo/one', component: SomeComponent},
{path:'/foo/two', component: SomeComponent},
{path:'/foo/three', component: SomeComponent},
{path:'/bar', component: SomeComponent},
]
For the example above, this should do.
const result = []
arr.map((obj) => {
if (obj.children) {
const el = {...obj, ...{}}
delete el.children
result.push(el)
Object.values(obj.children).map((v, i) => {
result.push(v)
})
} else {
result.push(obj)
}
})
console.log(result)
So there's Array.prototype.flat, but that doesn't deal with lists of Objects where one key (how should it know, which) should be flattened.
But you can always resort to Array.prototype.reduce to achieve that yourselves:
const SomeComponent = 'SomeComponent';
const arr = [
{path:'/foo', component: SomeComponent, children: [
{path:'/one', component: SomeComponent},
{path:'/two', component: SomeComponent},
{path:'/three', component: SomeComponent}
]},
{path: '/bar', component: SomeComponent}
];
function myFlat(a, prefix = '') {
return a.reduce(function (flattened, {path, component, children}) {
path = prefix + path;
return flattened
.concat([{path, component}])
.concat(children ? myFlat(children, path) : []);
}, []);
}
console.log(myFlat(arr));
You can try this
flattenArr = arr => {
const result = [];
arr.forEach(item => {
const {path, component, children} = item;
result.push({path, component});
if (children)
result.push(...flattenArr(children));
});
return result;
}
I recently had to solve this problem to create a nested dropdown and here is my solution in case some one need to have the parents history tracked to do things like
and also be able to send back to the Back-End every ID that is necessary
I am sharing the pure flatten and flatten on steroids version to record the parents
P.P.S. the code can easily be reused to create "path" just concat the data you need instead of keeping it an array as I had to do.
const items = [
{ id: 1, title: 'one', children: [{ id: 3, title: 'one`s child', children: [] }] },
{ id: 2, title: 'two', children: [] },
{
id: 4,
title: 'three',
children: [
{
id: 5,
title: 'three`s child',
children: [
{
id: 6,
title: 'three`s grandchild',
children: [{ id: 7, title: 'three`s great-grandchild', children: [] }],
},
],
},
],
},
]
/**
* #param items - [{..., children: ...}]
* #info children key is remove and parents is set instead
* #returns flatten array and remember parents in array
*/
const deepFlattenRememberParents = (items) => {
const flatten = JSON.parse(JSON.stringify(items)) // Important - create a deep copy of 'items' / preferably use lodash '_.cloneDeep(items)', but for the example this will do
for (let i = 0; i < flatten.length; i++) {
if (flatten[i].hasOwnProperty('children')) {
flatten[i].children.map((child) => {
if (flatten[i].hasOwnProperty('parents')) {
child.parents = [...flatten[i].parents, flatten[i]]
} else {
child.parents = [flatten[i]]
}
return child
})
flatten.splice(i + 1, 0, ...flatten[i].children)
delete flatten[i].children
}
if (!flatten[i].hasOwnProperty('parents')) {
flatten[i].parents = []
}
}
return flatten
}
/**
* #param items - [{..., children: ...}]
* #returns flatten array
*/
const deepFlatten = (items) => {
const flatten = JSON.parse(JSON.stringify(items)) // Important - create a deep copy of 'items' / preferably use lodash '_.cloneDeep(items)', but for the example this will do
for (let i = 0; i < flatten.length; i++) {
if (flatten[i].hasOwnProperty('children')) {
flatten.splice(i + 1, 0, ...flatten[i].children)
delete flatten[i].children
}
}
return flatten
}
console.log('deepFlattenRememberParents ', deepFlattenRememberParents(items))
console.log('deepFlatten ', deepFlatten(items))
Related
I need to create a nested array using the path as reference for the children.
E.g: 4.1 is a child of 4, 4.1.1 is a child of 4.1, 4.2 is a child of 4...
I have this flat array, with all the data and paths. How would be the best approach to create a nested array where the children are nested to its parent based on its path.
Input:
const list = [
{
location: 1,
path: '4'
},
{
location: 2,
path: '4.1'
},
{
location: 3,
path: '4.1.1'
},
{
location: 4,
path: '4.1.2'
},
{
location: 5,
path: '4.2'
},
{
location: 6,
path: '4.2.1'
},
{
location: 7,
path: '4.3'
},
{
location: 8,
path: '4.3.1'
}
];
Output:
const list = [
{
location: 1,
path: '4',
children: [
{
location: 2,
path: '4.1',
children: [
{
location: 3,
path: '4.1.1'
},
{
location: 4,
path: '4.1.2'
},
]
},
{
location: 5,
path: '4.2',
children: [
{
location: 6,
path: '4.2.1'
},
]
},
{
location: 7,
path: '4.3',
children: [
{
location: 8,
path: '4.3.1'
}
]
},
]
},
];
The best approach would be something recursive.
Any suggestions for this algorithm?
I was curious if the linked answer from Scott would be able to solve this problem without modification. It does!
import { tree } from './Tree'
import { bind } from './Func'
const parent = (path = "") =>
bind
( (pos = path.lastIndexOf(".")) =>
pos === -1
? null
: path.substr(0, pos)
)
const myTree =
tree // <- make tree
( list // <- array of nodes
, node => parent(node.path) // <- foreign key
, (node, children) => // <- node reconstructor
({ ...node, children: children(node.path) }) // <- primary key
)
console.log(JSON.stringify(myTree, null, 2))
[
{
"location": 1,
"path": "4",
"children": [
{
"location": 2,
"path": "4.1",
"children": [
{
"location": 3,
"path": "4.1.1",
"children": []
},
{
"location": 4,
"path": "4.1.2",
"children": []
}
]
},
{
"location": 5,
"path": "4.2",
"children": [
{
"location": 6,
"path": "4.2.1",
"children": []
}
]
},
{
"location": 7,
"path": "4.3",
"children": [
{
"location": 8,
"path": "4.3.1",
"children": []
}
]
}
]
}
]
The Tree module is shared in this post and here's a peek at the Func module that supplies bind -
// Func.js
const identity = x => x
const bind = (f, ...args) =>
f(...args)
const raise = (msg = "") => // functional throw
{ throw Error(msg) }
// ...
export { identity, bind, raise, ... }
Expand the snippet below to verify the results in your browser -
// Func.js
const bind = (f, ...args) =>
f(...args)
// Index.js
const empty = _ =>
new Map
const update = (r, k, t) =>
r.set(k, t(r.get(k)))
const append = (r, k, v) =>
update(r, k, (all = []) => [...all, v])
const index = (all = [], indexer) =>
all.reduce
( (r, v) => append(r, indexer(v), v)
, empty()
)
// Tree.js
// import { index } from './Index'
function tree (all, indexer, maker, root = null)
{ const cache =
index(all, indexer)
const many = (all = []) =>
all.map(x => one(x))
const one = (single) =>
maker(single, next => many(cache.get(next)))
return many(cache.get(root))
}
// Main.js
// import { tree } from './Tree'
// import { bind } from './Func'
const parent = (path = "") =>
bind
( (pos = path.lastIndexOf(".")) =>
pos === -1
? null
: path.substr(0, pos)
)
const list =
[{location:1,path:'4'},{location:2,path:'4.1'},{location:3,path:'4.1.1'},{location:4,path:'4.1.2'},{location:5,path:'4.2'},{location:6,path:'4.2.1'},{location:7,path:'4.3'},{location:8,path:'4.3.1'}]
const myTree =
tree
( list // <- array of nodes
, node => parent(node.path) // <- foreign key
, (node, children) => // <- node reconstructor
({ ...node, children: children(node.path) }) // <- primary key
)
console.log(JSON.stringify(myTree, null, 2))
One way to do this is to use an intermediate index mapping paths to objects, then folding your list into a structure by looking up each node and its parent in the index. If there is no parent, then we add it to the root object. In the end, we return the children of our root object. Here's some code for that:
const restructure = (list) => {
const index = list .reduce(
(a, {path, ...rest}) => ({...a, [path]: {path, ...rest}}),
{}
)
return list .reduce((root, {path}) => {
const node = index [path]
const parent = index [path .split('.') .slice(0, -1) .join('.')] || root
parent.children = [...(parent.children || []), node]
return root
}, {children: []}) .children
}
const list = [{location: 1, path: '4'}, {location: 2, path: '4.1' }, {location: 3, path: '4.1.1'}, {location: 4, path: '4.1.2'}, {location: 5, path: '4.2'}, {location: 6, path: '4.2.1'}, {location: 7, path: '4.3'}, {location: 8, path: '4.3.1'}]
console.log (restructure (list))
.as-console-wrapper {min-height: 100% !important; top: 0}
Using the index means that we don't have to sort anything; the input can be in any order.
Finding the parent involves replacing, for instance, "4.3.1" with "4.3" and looking that up in the index. And when we try "4", it looks up the empty string, doesn't find it and uses the root node.
If you prefer regex, you could use this slightly shorter line instead:
const parent = index [path.replace (/(^|\.)[^.]+$/, '')] || root
But, you might also want to look at a more elegant technique in a recent answer on a similar question. My answer here, gets the job done (with a bit of ugly mutation) but that answer will teach you a lot about effective software development.
You can first sort the array of objects by path so that the parent will always be before it's children in the sorted array.
eg: '4' will be before '4.1'
Now, you can create an object where the keys are the paths. Let's assume '4' is already inserted in our object.
obj = {
'4': {
"location": 1,
"path": "4",
}
}
When we process '4.1', we first check if '4' is present in our object. If yes, we now go into its children (if the key 'children' isn't present, we create a new empty object) and check if '4.1' is present. If not, we insert '4.1'
obj = {
'4': {
"location": 1,
"path": "4",
"children": {
"4.1": {
"location": 2,
"path": "4.1"
}
}
}
}
We repeat this process for each element in list. Finally, we just have to recursively convert this object into an array of objects.
Final code:
list.sort(function(a, b) {
return a.path - b.path;
})
let obj = {}
list.forEach(x => {
let cur = obj;
for (let i = 0; i < x.path.length; i += 2) {
console.log(x.path.substring(0, i + 1))
if (x.path.substring(0, i + 1) in cur) {
cur = cur[x.path.substring(0, i + 1)]
if (!('children' in cur)) {
cur['children'] = {}
}
cur = cur['children']
} else {
break;
}
}
cur[x.path] = x;
})
function recurse (obj) {
let res = [];
Object.keys(obj).forEach((key) => {
if (obj[key]['children'] !== null && typeof obj[key]['children'] === 'object') {
obj[key]['children'] = recurse(obj[key]['children'])
}
res.push(obj[key])
})
return res;
}
console.log(recurse(obj));
was thinking along the same terms as Aadith but came up from an iterative approach. I think the most performant way to do it is to use a linked list structure and then flatten it though.
const list = [
{
location: 1,
path: '4'
},
{
location: 2,
path: '4.1'
},
{
location: 3,
path: '4.1.1'
},
{
location: 4,
path: '4.1.2'
},
{
location: 5,
path: '4.2'
},
{
location: 6,
path: '4.2.1'
},
{
location: 7,
path: '4.3'
},
{
location: 8,
path: '4.3.1'
}
];
let newList = [];
list.forEach((location) =>
{
console.log('Handling location ',location);
if(location.path.split('.').length==1)
{
location.children = [];
newList.push(location);
}
else
{
newList.forEach(loc => {
console.log('checking out: ',loc);
let found = false;
while(!found)
{
console.log(loc.path,'==',location.path.substring(0, location.path.lastIndexOf('.')));
found = loc.path == location.path.substring(0, location.path.lastIndexOf('.'));
if(!found)
{
for(let i=0;i<loc.children.length;i++)
{
let aloc = loc.children[i];
found = aloc.path == location.path.substring(0, location.path.lastIndexOf('.'));
if(found)
{
console.log('found it...', loc);
location.children = [];
aloc.children.push(location);
break;
}
}
}
else
{
console.log('found it...', loc);
location.children = [];
loc.children.push(location);
}
}
} );
}
});
console.log(newList);
This was my quick and dirty way of how to go about it
Thank you for all the suggestions!
I could definitely see really sophisticated solutions to my problem.
By the end of the day, I've ended up creating my own "dirty" solution.
It is definitely a slower approach, but for my application this list won't be long to the point where i should be too worry about optimization.
I had simplified the flatted list for the purpose of my question. Although, in reality the list was a little more complex then that. I could also pass the path I want to find its children.
This is the solution that worked for me.
function handleNested(list, location) {
return list.map((item) => {
if (item.children && item.children.length > 0) {
item.children = handleNested(item.children, location);
}
if (
location.path.startsWith(item.path) &&
location.path.split(".").length === item.path.split(".").length + 1
) {
return {
...item,
children: item.children ? [...item.children, location] : [location],
};
} else {
return item;
}
});
}
function locationList(path) {
// Filtering the list to remove items with different parent path, and sorting by path depthness.
const filteredList = list
.filter((location) => location.path.startsWith(path))
.sort((a, b) => a.path.length - b.path.length);
let nestedList = [];
// Looping through the filtered list and placing each item under its parent.
for (let i = 0; i < filteredList.length; i++) {
// Using a recursive function to find its parent.
let res = handleNested(nestedList, filteredList[i]);
nestedList = res;
}
return nestedList;
}
locationList("4");
Given the following data structure:
[
{
"name":"root",
"children":[
{
"name":"de",
"children":[
{
"name":"de",
"children":[
{
"name":"project-1",
"children":[
]
},
{
"name":"project-2",
"children":[
]
}
]
}
]
}
]
}
]
Expected:
[
{
"name":"project-1",
"children":[
]
},
{
"name":"project-2",
"children":[
]
}
]
I want to remove a level if there is only one child. In this example I want to have a new array that only contains the children of the "root" level without root itself.
I would do that with reduce but still cant wrap my head around reduce in combination with recursion. Any ideas?
You can simply use map and flatten arrays afterwards.
.map(o => o.children).flat()
EDIT: updated answer after figuring out the real question
Still you can use map and flatten logic but in a recursive manner.
function removeSingleChildElms (o) {
if (!o.children) return
if (o.children.length === 1) {
return o.children.map(removeSingleChildElms).flat()
} else {
return o.children
}
}
EDIT2:
Some explanation: The problem is transforming array of object(s) into array of different objects. I don't choose reduce, because the problem doesn't care about relationship/logic among sibling elements. It's just about transforming, hence map will just work good enough.
The problem asks to 'skip' objects with 1 child. This is recurring part, meaning: If you see an object satisfying this condition you go deeper for mapping. In any other valid condition, children stay same (else case)
Tree transformation can be made easy by breaking the task down into two parts:
a function for transforming a single node
a function for transforming an array of nodes
To transform a single node, we write transform1
if there are no children, we have found a leaf node, return the singleton node
if there is just one child, drop the node and return the transformation of its only child
otherwise, the node has multiple children, call our second function transformAll
const transform1 = ({ children = [], ...node }) =>
children.length === 0 // leaf
? [ node ]
: children.length === 1 // singleton
? transform1 (...children)
: transformAll (children) // default
To transform an array of nodes, we write transformAll -
const transformAll = (arr = []) =>
arr .flatMap (transform1)
As you can see, transformAll calls transform1, which also calls transformAll. This technique is called mutual recursion and it's a great way to process recursive data structures like the one proposed in your question.
To ensure our function works properly, I've modified the tree to contain more data scenarios. Note, our program works for any nodes with a children property. All other properties are displayed in the result -
const data =
[ { name: "a"
, children:
[ { name: "a.a"
, children:
[ { name: "a.a.a"
, children: []
}
, { name: "a.a.b"
, foo: 123
, children: []
}
]
}
]
}
, { name: "b"
, children:
[ { name: "b.a"
, children:
[ { name: "b.a.a"
, children: []
}
, { name: "b.a.b"
, children: []
}
]
}
, { name: "b.b"
, children: []
}
]
}
, { name: "c"
, children: []
}
]
We can run transformAll on your data to transform all of the nodes -
transformAll (data)
// [ { name: 'a.a.a' }
// , { name: 'a.a.b', foo: 123 }
// , { name: 'b.a.a' }
// , { name: 'b.a.b' }
// , { name: 'b.b' }
// , { name: 'c' }
// ]
Or to transform a single node, we call transform1 -
transform1 (data[0])
// [ { name: 'a.a.a' }
// , { name: 'a.a.b', foo: 123 }
// ]
transform1 (data[2])
// [ { name: 'c' } ]
Expand the snippet below to verify the results in your own browser -
const data =
[ { name: "a"
, children:
[ { name: "a.a"
, children:
[ { name: "a.a.a"
, children: []
}
, { name: "a.a.b"
, foo: 123
, children: []
}
]
}
]
}
, { name: "b"
, children:
[ { name: "b.a"
, children:
[ { name: "b.a.a"
, children: []
}
, { name: "b.a.b"
, children: []
}
]
}
, { name: "b.b"
, children: []
}
]
}
, { name: "c"
, children: []
}
]
const transform1 = ({ children = [], ...node }) =>
children.length === 0 // leaf
? [ node ]
: children.length === 1 // singleton
? transform1 (...children)
: transformAll (children) // default
const transformAll = (arr = []) =>
arr .flatMap (transform1)
console .log (transformAll (data))
// [ { name: 'a.a.a' }
// , { name: 'a.a.b', foo: 123 }
// , { name: 'b.a.a' }
// , { name: 'b.a.b' }
// , { name: 'b.b' }
// , { name: 'c' }
// ]
I am basically trying to convert a flat json file to tree view. Here the parent child relationship required for tree view is mentained by links key using source and target.
Here is the sample raw input:
{
"nodes" : [
{
name: "bz_db",
index: 0
},
{
name: "mysql",
index: 1
},
{
name: "postgres",
index: 2
},
{
name: "it-infra",
index: 3
},
{
name: "user-count",
index: 4
}
],
links: [
{
source: 0, target: 1
},
{
source: 0, target: 3
},
{
source: 1, target: 3
},
{
source: 3, target: 4
}
]
}
As you can see the link field maintains this relation ship, and finally I want my data in this format:
{
name: "bz_db",
children: [
{
name: "mysql",
children: [
{
name: "it-infra",
children: [
{
name: "user_count",
children: []
}
]
}
]
},
{
name: "it-infra",
children: [{
name: "user_count",
children: []
}
]
}
]
}
I tried to solve this, but it worked for 1 level (to show immediate child of a selected root element.
var findObjectByKeyValue = function(arrayOfObject, key, value){
return _.find(arrayOfObject, function(o){ return o[key] == value})
}
var rootObject = findObjectByKeyValue(sample_raw_input.nodes, 'name', 'bz_db');
var treeObject = {
name: rootObject.name,
index: rootObject.index,
root: true,
children: []
};
angular.forEach(dependencyData.links, function(eachLink){
if(treeObject.index == eachLink.source){
var rawChildObject = findObjectByKeyValue(dependencyData.nodes, 'index', eachLink.target);
var childObject = {};
childObject.index = rawChildObject.index;
childObject.name = rawChildObject.name;
childObject.children = [];
treeObject.children.push(childObject);
}
});
But the above code returns me only first level of depndencies, but i want hierarchical relationship.
I know i can use recursion here. But I am not so comfortable with it.
Josh's answer uses a sequence of map->filter->map->find calls, each of which iterate thru a collection of data. This loop of loop of loop of loops results in a stunning amount of computational complexity as the number of nodes in your collection increases.
You can dramatically simplify the creation of the tree by using a single reduce pass on each nodes and links. Map can also perform look-ups in logarithmic time, compared to Array's find which requires linear time (slower). When you consider this operation is called for each element of the input, it's clear to see a significant difference in time.
const makeTree = (nodes = [], links = []) =>
links.reduce
( (t, l) =>
t.set ( l.source
, MutableNode.push ( t.get (l.source)
, t.get (l.target)
)
)
, nodes.reduce
( (t, n) => t.set (n.index, MutableNode (n.name))
, new Map
)
)
.get (0)
Lastly, we provide the MutableNode interface we relied upon
const MutableNode = (name, children = []) =>
({ name, children })
MutableNode.push = (node, child) =>
(node.children.push (child), node)
Below is a full program demonstration. JSON.stringify is used only to display the result
const MutableNode = (name, children = []) =>
({ name, children })
MutableNode.push = (node, child) =>
(node.children.push (child), node)
const makeTree = (nodes = [], links = []) =>
links.reduce
( (t, l) =>
t.set ( l.source
, MutableNode.push ( t.get (l.source)
, t.get (l.target)
)
)
, nodes.reduce
( (t, n) => t.set (n.index, MutableNode (n.name))
, new Map
)
)
.get (0)
const data =
{ nodes:
[ { name: "bz_db", index: 0 }
, { name: "mysql", index: 1 }
, { name: "postgres", index: 2 }
, { name: "it-infra", index: 3 }
, { name: "user-count", index: 4 }
]
, links:
[ { source: 0, target: 1 }
, { source: 0, target: 3 }
, { source: 1, target: 3 }
, { source: 3, target: 4 }
]
}
const tree =
makeTree (data.nodes, data.links)
console.log (JSON.stringify (tree, null, 2))
You can rely on tracking an object reference and do this without any recursion. Using Object.assign, map the list of nodes to its children:
// Assuming that input is in `input`
const nodes = input.nodes.reduce((a, node) => {
a[node.index] = { ...node, index: undefined };
return a;
}, []);
// organize the links by their source
const links = input.links.reduce((a, link) => {
return a.set((a.get(link.source) || []).concat(nodes[link.target]);
}, new Map());
// Apply side effect of updating node children
nodes.forEach(node => Object.assign(node, {
children: links.get(node.index),
}));
So I'm taking the list of nodes, and assigning to each (to mutate the node itself -- keep in mind this is a side-effect) a new array. Those children are all the links that link this node, and we Array#map them to convert their target ID into the actual node we want.
just share sample, a little different from yours.
But it give you a hint with recursive function.
jsFiddle flat array json transform to recursive tree json
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].parent == parent) {
var children = getNestedChildren(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
var flat = [
{id: 1, title: 'hello', parent: 0},
{id: 2, title: 'hello', parent: 0},
{id: 3, title: 'hello', parent: 1},
{id: 4, title: 'hello', parent: 3},
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4},
{id: 7, title: 'hello', parent: 3},
{id: 8, title: 'hello', parent: 2}
]
var nested = getNestedChildren(flat, 0)
console.log(nested)
I am trying to add id:s to objects in an array with Ramda, but the id just equals 1 for every object.
let i = 1;
return R.evolve({
cms: {
components: R.map(R.assoc('id', i++)),
},
}, state),
I assume that has something to do with the i++. One is not supposed to mutate like that with Ramda.
But then, how would I do this correctly.
You have to wrap it in a function, otherwise i++ is evaluated once and then applied to all your elements.
const state = {
cms: {
components: [{
name: 'Serge'
}, {
name: 'Odile'
}, {
name: 'Simon'
}, {
name: 'Émile'
}]
}
};
let i = 1,
modifiedState = R.evolve({
cms: {
components: R.map((element) => R.assoc('id', i++, element)),
},
}, state);
console.log(modifiedState.cms.components);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Or you could use Ramda's addIndex like this:
const transform = R.evolve({
cms: {
components: R.addIndex(R.map)((comp, i) => R.assoc('id', i + 1, comp)),
},
})
const state = {
cms: {
components: [
{name: 'Serge'},
{name: 'Odile'},
{name: 'Simon'},
{name: 'Émile'}
]
}
};
console.log(transform(state))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
const List = Immutable.List;
const items = [
{ id: 1, subList: [] },
{ id: 2, subList: [] },
{ id: 3, subList: [] }
];
const newItem = { name: "sublist item" };
let collection = List(items);
collection = collection.updateIn([0, 'subList'], function (items) {
return items.concat(newItem)
});
https://jsbin.com/midimupire/edit?html,js,console
Results in:
Error: invalid keyPath
I think that perhaps I need to set subList as a List(); I get the same error when trying this.
If I understand the question correctly, you want to return collection with the first element as:
{
id : 1,
subList: [
{name: "sublist item"}
]
}
To do this we'll need to make a few changes.
Use Immutable.fromJS to deeply convert the plain JS array of objects to an Immutable List of Maps
Use List.update() to return a new List with the updated value
Use Map.updateIn() to return a new LMapist with the updated value
Here's the whole thing:
const List = Immutable.List;
const items = [{
id: 1,
subList: []
},
{
id: 2,
subList: []
},
{
id: 3,
subList: []
}
];
const newItem = {
name: "sublist item"
};
let collection = Immutable.fromJS(items);
collection = collection.update(0, item => {
return item.updateIn(['subList'], subList => {
return subList.concat(newItem);
});
});
console.log(collection)
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>
And the result:
[
{
"id": 1,
"subList": [
{
"name": "sublist item"
}
]
},
{
"id": 2,
"subList": []
},
{
"id": 3,
"subList": []
}
]
Update: List.updateIn() can use an index as the keypath, so you can simplify this to the following:
collection = collection.updateIn([0, 'subList'], subList => {
return subList.concat(newItem);
});
Like this:
const List = Immutable.List;
const items = [{
id: 1,
subList: []
},
{
id: 2,
subList: []
},
{
id: 3,
subList: []
}
];
const newItem = {
name: "sublist item"
};
let collection = Immutable.fromJS(items);
collection = collection.updateIn([0, 'subList'], subList => {
return subList.concat(newItem);
});
console.log(collection)
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.2/immutable.js"></script>
Use the object you got, update the subArray and return the whole object.
const List = Immutable.List;
const items = [
{ id: 1, subList: [] },
{ id: 2, subList: [] },
{id: 3, subList: [] }
];
const newItem = { name: "sublist item" };
let collection = List(items);
collection = collection.update([0], function (obj) {
obj.subList = obj.subList.concat(newItem)
return obj;
});
This doesn’t work because the elements of your Immutable.List are plain-old JavaScript objects (POJO), not Immutable.Maps, so updateIn doesn’t know how to work with them. You can either:
Make the objects Immutable.Maps by using Immutable.fromJS instead of Immutable.List as the constructor to convert the entire object graph to Immutable objects. (See JS Bin)
Use update([0]) instead of updateIn to just get the POJO and mutate it (as in #Navjot’s answer).