React.Js. Mapping in nested array to update state - javascript

In React.js I have array of object:
arr = [
{
type: 'folder',
name: 'src',
id: '1',
files: [
{
type: 'folder',
name: 'name 1',
id: '2',
files: [
{ type: 'file', name: 'JSFile.js', id: '3' },
],
},
{
type: 'folder',
name: 'name 2 ',
id: '6',
files: [
{ type: 'file', name: 'File.js', id: '7' },
{ type: 'file', name: 'JSXfile.jsx', id: '14' },
type: 'folder',
name: 'name 1',
id: '6',
files: [
{ type: 'file', name: 'HTMLfile.html', id: '5' },
],
},
],
},
],
},
]
I have an array of objects, and I want to update this array with some object, for example something like this:
const onAddFile = () => {
const newFile = {
type: 'file',
name: 'SXfile',
id: "100,
}
const folderToPush = files: (1) [{…}],
id: "6",
name: "name 1 ",
type: "folder" <---this is folder which I have selected via click, which I have id of folder and array of files that I have push newFile. and update state instantly.
folderToPush.files.push(newFile)
}
expected output is something like this:
arr = [
{
type: 'folder',
name: 'src',
id: '1',
files: [
{
type: 'folder',
name: 'name 1',
id: '2',
files: [
{ type: 'file', name: 'JSFile.js', id: '3' },
],
},
{
type: 'folder',
name: 'name 2 ',
id: '6',
files: [
{ type: 'file', name: 'File.js', id: '7' },
{ type: 'file', name: 'JSXfile.jsx', id: '14' },
type: 'folder',
name: 'name 1',
id: '6',
files: [
{ type: 'file', name: 'HTMLfile.html', id: '5' },
{ type: 'file', name: 'SXfile.jsx', id: '100' }, <--- here is pushed object
],
},
],
},
],
},
]
I know it have to be some recursive function to map through whole array, but I have no idea how to implement this. Is there a way to make it, with useState only to see changes instantly?

Related

How to pass tests correctly in Typescript?

The problem:
I'm trying to write a TypeScript function suitable for a previously written JEST test. There are 5 tests in total. The function I wrote passes all the tests, but unfortunately, I was asked to write again in the review. How to write this more efficiently?
Example case:
Imagine an array that contains folders. These folders can have files in it. move function moves a file to another folder and returns the new state of given list.
Example list:
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
]
If I run move(list, '4', '6') then I expect file with id 4 moved to the folder which has id 6. Function should return the new state below;
const result = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [
{ id: '7', name: 'File 5' },
{ id: '4', name: 'File 3' },
],
},
]
move.ts
type File = {
id: string;
name: string;
};
type Folder = {
id: string;
name: string;
files: File[];
};
type List = Folder[];
export default function move(list: List, source: string, destination: string): List {
let sourceFileIndex = -1;
let sourceFolderIndex = -1;
let destinationFolderIndex = -1;
for (let i = 0; i < list.length; i += 1) {
const { id, files } = list[i];
if (id === source) {
throw new Error('You cannot move a folder');
}
if (id === destination) {
destinationFolderIndex = i;
}
if (destinationFolderIndex > -1 && sourceFileIndex > -1) break;
for (let j = 0; j < files.length; j += 1) {
if (files[j].id === destination) {
throw new Error('You cannot specify a file as the destination');
}
if (files[j].id === source) {
sourceFileIndex = j;
sourceFolderIndex = i;
break;
}
}
}
if (destinationFolderIndex === -1) {
throw new Error('Given destination id did not match any folder');
}
if (sourceFolderIndex === -1) {
throw new Error('Given source id of file was not found in any folder');
}
list[destinationFolderIndex].files.push(
...list[sourceFolderIndex].files.splice(sourceFileIndex, 1),
);
return list;
}
move.spec.ts
import move from './move';
describe('move', () => {
it('moves given file to another folder', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
const result = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [
{ id: '7', name: 'File 5' },
{ id: '4', name: 'File 3' },
],
},
];
expect(move(list, '4', '6')).toStrictEqual(result);
});
it('throws error if given source is not a file', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [{ id: '2', name: 'File 1' }],
},
{ id: '3', name: 'Folder 2', files: [] },
];
expect(() => move(list, '3', '1')).toThrow('You cannot move a folder');
});
it('throws error if given destination is not a folder', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [{ id: '2', name: 'File 1' }],
},
{ id: '3', name: 'Folder 2', files: [{ id: '4', name: 'File 2' }] },
];
expect(() => move(list, '2', '4')).toThrow('You cannot specify a file as the destination');
});
it('throws error if given source was not found', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
expect(() => move(list, '9', '6')).toThrow(
'Given source id of file was not found in any folder',
);
});
it('throws error if given destination was not found', () => {
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
];
expect(() => move(list, '4', '9')).toThrow('Given destination id did not match any folder');
});
});
As I said, it passed 5 tests but was not accepted. What is the problem?

Changing nested object's structure

I want to convert an object from one format to another. So far my attempts at doing this recursively failed; either I'm getting a maximum stack exception or I'm unable to iterate over all paths.
Let's assume we have an object that lists questions and their answers. There may be N questions and M answers.
Object at start:
var before = {
item: 'Question 1',
id: '1',
type: 'Question',
items: [
{
item: 'Answer 1',
id: '1.1',
type: 'Answer',
items:
[
{
item: 'Question 2',
id: '1.1.1',
type: 'Question',
items: [
{
item: 'Answer 2.1',
id: '1.1.1.1',
type: 'Answer'
},
{
item: 'Answer 2.2',
id: '1.1.1.2',
type: 'Answer'
}
]
}
// ...
]
}, {
item: 'Answer 1',
id: '1.2',
type: 'Answer',
items:
[
{
item: 'Question 3',
id: '1.2.1',
type: 'Question',
items: [
{
item: 'Answer 3.1',
id: '1.2.1.1',
type: 'Answer'
},
{
item: 'Answer 3.2',
id: '1.2.1.2',
type: 'Answer'
}
]
}
// ...
]
}
// ...
]
}
Object how it should look like (wrap all in 'items' array; change key names 'item' to 'title', 'id' to 'key', remove 'type', add 'color' depending on 'type'):
var after = {
items: [
{
title: 'Question 1',
key: '1',
color: 'Red',
items: [
{
title: 'Answer 1',
key: '1.1',
color: 'Blue',
items:
[
{
title: 'Question 2',
key: '1.1.1',
color: 'Red',
items: [
{
title: 'Answer 2.1',
key: '1.1.1.1',
color: 'Blue'
},
{
title: 'Answer 2.2',
key: '1.1.1.2',
color: 'Blue'
}
]
}
// ...
]
}, {
title: 'Answer 1',
key: '1.2',
color: 'Blue',
items:
[
{
title: 'Question 3',
key: '1.2.1',
color: 'Red',
items: [
{
title: 'Answer 3.1',
key: '1.2.1.1',
color: 'Blue'
},
{
title: 'Answer 3.2',
key: '1.2.1.2',
color: 'Blue'
}
]
}
// ...
]
}
// ...
]
}
]
}
It seems easy enough, but I can't get it to work. This is how I tried to iterate:
function iter(o) {
for(let k in o) {
if (!(['item', 'items', 'id'].includes(k))) // My object contains a few more keys I don't want to go down further into
continue
if (o[k] !== null && typeof o[k] === 'object') {
iter(o[k]); // Max stack exception
return;
}
}
};
Thank you very much!
You could map the objects and rename the keys and map nested items.
const
iter = ({ item: title, id: key, type, items, ...o }) => ({
title,
key,
color: 'color' + key,
...o,
...(items && { items: items.map(iter) })
}),
before = { item: 'Question 1', id: '1', type: 'Question', items: [{ item: 'Answer 1', id: '1.1', type: 'Answer', items: [{ item: 'Question 2', id: '1.1.1', type: 'Question', items: [{ item: 'Answer 2.1', id: '1.1.1.1', type: 'Answer' }, { item: 'Answer 2.2', id: '1.1.1.2', type: 'Answer' }] }] }, { item: 'Answer 1', id: '1.2', type: 'Answer', items: [{ item: 'Question 3', id: '1.2.1', type: 'Question', items: [{ item: 'Answer 3.1', id: '1.2.1.1', type: 'Answer' }, { item: 'Answer 3.2', id: '1.2.1.2', type: 'Answer' }] }] }] },
result = [before].map(iter);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can achieve this using map, I wrote an simple test to show my point here
this is the important part of the code
function rename(item: any) {
return {
title: item.item,
key: item.id,
color: item.type === 'Question' ? 'red' : 'blue',
items: item.items?.map(rename)
}
}
console.log(items.map(rename))
Of course if you're using typescript, change any to the appropriate type and pay attention that I'm using ? operator which will not work with javascript, so you could do something like
...
items: item.items ? item.items.map(rename) : undefined
...

Expandable dynamic nested list with objects array

Im struggling with expandable nested array list anyone keen to help me out
https://jsfiddle.net/AccordEuro06/2otnxb9c/3/
const webfolder = [{
name: 'Webpage',
type: 'folder',
children: [{
name: 'php',
type: 'folder',
children: [{
name: 'header.php',
type: 'file'
},
{
name: 'footer.php',
type: 'file'
},
]
},
{
name: 'css',
type: 'folder',
children: [{
name: 'images',
type: 'folder',
children: [{
name: 'logos',
type: 'folder',
children: [{
name: 'Colored.svg',
type: 'file'
},
{
name: 'Monochrome.svg',
type: 'file'
},
]
},
{
name: 'gallery',
type: 'folder',
children: [{
name: 'image1.jpg',
type: 'file'
},
{
name: 'image2.png',
type: 'file'
},
{
name: 'image3.gif',
type: 'file'
},
{
name: 'image4.jpeg',
type: 'file'
},
{
name: 'image5.jpg',
type: 'file'
},
]
},
{
name: 'about-us.jpg',
type: 'file'
},
],
},
{
name: 'style.css',
type: 'file'
},
]
},
{
name: 'javascipt',
type: 'folder',
children: [{
name: 'script.js',
type: 'file'
},
{
name: 'script2.js',
type: 'file'
},
{
name: 'ajax.js',
type: 'file'
},
{
name: 'jquery.js',
type: 'file'
},
]
},
{
name: 'index.html',
type: 'file'
}
]
}];
function listHtml(children) {
return '<ul>' + children.map(node =>
'<li>' + node.name +
(node.type === 'file' ? '' : listHtml(node.children)) +
'</li>').join('\n') +
'</ul>';
}
document.getElementById('expandableTree').innerHTML += listHtml(webfolder);
I understand that hide/show on click should be done with css but i ran out of ideas how to add proper classes to branches and leafs
You can use the HTML5 <details> elements:
function listHtml(children) {
return '<ul>' + children.map(node =>
`<li>
${node.type === 'file'
? node.name
: `<details>
<summary>${node.name}</summary>
${listHtml(node.children)}
</details>`
}
</li>`
).join('\n') + '</ul>';
}
document.getElementById('expandableTree').innerHTML += listHtml(webfolder);
Try it on CodePen

How to get number of items in array which have the same "parent" element?

I gave an array of items which looks like this
const array = [{
type: 'section',
name: 'name 1',
id: '1'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
name: 'name 1',
id: '1'
}
}
},
{
type: 'item',
name: 'item 2',
data: {
section: {
id: '1',
name: 'name 1'
}
}
},
{
type: 'section',
name: 'name 2',
id: '3'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
id: '3',
name: 'name 2'
}
}
},
{
type: 'section',
name: 'name 3'
}, {
type: 'section',
name: 'name 4'
},
]
I need to count and add to each object of type section the number of items that it has (numberOfItems: 3). The array is built that way that each section has its items follow it.
I also have a separate array of items which basically looks like that
const items = [{
'item_id: {
...
section: {
id: 'section_id'
name: 'section_name'
}
}]
I'm not really sure how to even start.
If I have understood your problem correctly then this code should do it:
const array = [
{
type: 'section',
name: 'name 1',
id: '1'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
name: 'name 1',
id: '1'
}
}
},
{
type: 'item',
name: 'item 2',
data: {
section: {
id: '1',
name: 'name 1'
}
}
},
{
type: 'section',
name: 'name 2',
id: '3'
},
{
type: 'item',
name: 'item 1',
data: {
section: {
id: '3',
name: 'name 2'
}
}
},
{
type: 'section',
name: 'name 3'
}, {
type: 'section',
name: 'name 4'
},
];
let section;
for (let item of array) {
console.log(item);
if (item.type === "section") {
section = item;
section.numberOfItems = 0;
} else if (item.type === "item" && section) {
section.numberOfItems++;
}
}
It steps through all elements, keeps a record when it finds a section and increments the count of the previously found section when it finds an item.

Filter array data

I'm new to ES6 and I have one problem, I have a nested array and I want to filter the data based on checked checkbox input.
const data = [
{ name: 'Airplane', type: 'folder', folderId: 1 },
{
name: 'Car',
type: 'folder',
folderId: 2,
children: [
{
Name: 'BMW',
type: 'folder',
folderId: 3,
children: [{ Name: 'X5', type: 'file', folderParentId: 3 }]
},
{ Name: 'Toyota', type: 'file', folderParentId: 2 },
{ Name: 'Mazda', type: 'file', folderParentId: 2 },
{ Name: 'Ferrari', type: 'file', folderParentId: 2 }
]
}
]
So I need to get:
CASE I: If X5 is checked and it is the only child, I need get the parent's information:
{
Name: 'BMW',
type: 'folder',
folderId: 3
}
CASE II: If Toyota and Ferrari are checked, I need to get the selected children:
{
Name: 'Toyota',
type: 'file',
folderParentId: 2
},
{
Name: 'Ferrari',
type: 'file',
folderParentId: 2
}
CASE III: If all children and all subfolders are checked, I need to get the selected grandparent:
{
name: 'Car',
type: 'folder',
folderId: 2,
}
After function for checkbox I got an array like:
const checked = [
{ name: 'Car', type: 'folder', folderId: 2}
{ name: 'BMW', type: 'folder', folderId: 2}
{ Name: 'X5', type: 'file', folderParentId: 3 }
{ Name: 'Toyota', type: 'file', folderParentId: 2 }
{ Name: 'Ferrari', type: 'file', folderParentId: 2 }
]
I try:
checked.filter(item=>item.folder === folderParentId && item)
You can use below function to get requested object.
function getObject(array, key, value) {
var o;
array.some(function iter(a) {
if (a[key] === value) {
o = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
});
return o;
}
You can test getObject(data,'name','Car');

Categories