I am trying to add an onChange function that can add and delete options in the cascader menu. I would like to be able to select existing menu options and add options on to them as well.
My example data structure looks like this:
const [dataTitle, setDataTitle] = useState([
{
label: '2022',
value: '2022',
children: [
{
label: 'Fall',
value: 'Fall',
children: [
{
label: 'Week 1',
value: 'Week 1',
children: [
{
label: 'Practice 1',
value: 'Practice 1',
},
{
label: 'Practice 2',
value: 'Practice 2',
},
{
label: 'Practice 3',
value: 'Practice 3',
},
{
label: 'Practice 4',
value: 'Practice 4',
},
{
label: 'Game 1',
value: 'Game 1',
},
]
},
{
label: 'Week 2',
value: 'Week 2',
},
{
label: 'Week 3',
value: 'Week 3',
},
],
},
],
},
]);
I am using these onChange events below to add a 'create-new-option' button and to add the new option when prompted.
const handleLabelChange = (value, selectedOptions) => {
const lastOption = selectedOptions[selectedOptions.length - 1];
// Check if the last selected option has a value of 'create-new-option'
if (lastOption.value === 'create-new-option') {
// Prompt user for new option name
const newOptionName = prompt('Enter a name for the new option:');
// Add the new option to the options data
const newOption = {
label: newOptionName,
value: newOptionName,
};
let newDataTitle = [...dataTitle];
let parentOption = newDataTitle.find(o => o.value === lastOption.parentValue);
if (!parentOption) {
parentOption = { children: [] };
newDataTitle.push(parentOption);
}
// Check if parentOption.children is defined before trying to push
if (parentOption.children) {
parentOption.children.push(newOption);
}
setDataTitle(newDataTitle);
}
};
const addCreateNewOption = (options) => {
return options.map(option => {
return {
...option,
children: option.children
? [...addCreateNewOption(option.children), {value: 'create-new-option', label: 'Create new option', parentValue: option.value}]
: [{value: 'create-new-option', label: 'Create new option', parentValue: option.value}]
};
});
};
My issue is having the new option display in the correct spot and under the correct parents. It works correctly with the second level (I can add a 'Spring' Option and it correctly shows up under 2022 / Spring) but none others.
CodeSandbox Recreation
Related
I have an array that looks something like this
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
As you can see, the organization name is something I want to key off of and create a data structure like this:
const output = [
// data.value will be their ID
{
organizationName: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' },
],
},
{
organizationName: 'Organization B',
data: [
{ label: 'Person 3', value: '3' },
],
},
]
What I've tried
I know I want to use reduce for something like this, but I feel like I'm off:
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
if (organizationName) {
acc.push({ organization: organizationName, data: [] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ name: externalPersonName, value: id });
}
return acc;
}, [] as any);
However the output comes out to something like this:
[
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization A', data: []},
{organizationName: 'Organization B', data: []},
];
data doesn't seem to get anything pushed inside the array in this reduce function, and the organization name get duplicated... what am I doing wrong?
Easiest way is to use an Map/Set/or object to keep track of orgs you create. This way you are not searching in the array to see if the organization was found already. After you are done, you can create the array you want from the object.
const externalPeople = {
data : [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
],
};
const providerOptions = Object.values(externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
name: externalPersonName,
id
} = currentValue;
// Is the org new? Yes, create an entry for it
if (!acc[organizationName]) {
acc[organizationName] = { organization: organizationName, data: [] };
}
// push the person to the organization
acc[organizationName].data.push({ name: externalPersonName, value: id });
return acc;
}, {}));
console.log(providerOptions)
Here is another solution
const example = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const result = example.reduce((res, entry) => {
const recordIndex = res.findIndex(rec => rec.organizationName === entry.organization.name);
if(recordIndex >= 0) {
res[recordIndex].data.push({ label: entry.name, value: entry.id});
} else {
const record = {
organizationName: entry.organization.name,
data: [{ label: entry.name, value: entry.id }]
};
res.push(record);
}
return res;
}, []);
console.log(result);
You are not checking if the value is already present in your accumulation acc
You can check it with a simple find in the if statement since it's an array
const providerOptions = externalPeople.data.reduce((acc, currentValue) => {
const {
organization: { name: organizationName },
} = currentValue;
//Check if organization is not present already
if (!acc.find(a => a.organization === organizationName)) {
//Add also the data of the element your are processing
acc.push({ organization: organizationName, data: [{label: currentValue.name, value: currentValue.id}] });
} else {
const { name: externalPersonName, id } = currentValue;
acc[acc.length - 1].data.push({ label: externalPersonName, value: id });
}
return acc;
}, [] as any);
I also added the data of the first element of the group you create when adding the organization.
The result should be as your expected output:
[
{
organization: 'Organization A',
data: [
{ label: 'Person 1', value: '1' },
{ label: 'Person 2', value: '2' }
]
},
{
organization: 'Organization B',
data: [
{ label: 'Person 3', value: '3' }
]
}
]
Hope it helps!
Compare this solution (using Lodash) with other solutions. Which one emphasises your intentions at most? This is why we use Lodash in our company - to maintain code as declarative as we can, because code readability, with minimum cognitive overload, is most important goal during coding.
const persons = [
{ id: '1', name: 'Person 1', organization: { id: '11', name: 'Organization A' } },
{ id: '2', name: 'Person 2', organization: { id: '12', name: 'Organization A' } },
{ id: '3', name: 'Person 3', organization: { id: '13', name: 'Organization B' } },
];
const personsByOrganizations = _.groupBy(persons, 'organization.name')
const output = _.map(personsByOrganizations, (persons, organizationName) => ({
organizationName,
data: _.map(persons, ({ name, id }) => ({
label: name,
value: id
}))
}))
Something like that with using a Set?
result = [...new Set(example.map(d => d.organization.name))].map(label => {
return {
organizationName: label,
data: example.filter(d => d.organization.name === label).map(d => {
return {label: d.name, value: d.id}
})
}
})
`
I have an array of objects that I'm trying to rebuild without any success:
const data = [
{
ID: 1,
TemplateName: 'Template 1',
TemplateCategory: 'Category A',
},
{
ID: 2,
TemplateName: 'Template 2',
TemplateCategory: 'Category A',
},
{
ID: 3,
TemplateName: 'Template 3',
TemplateCategory: 'Category B',
},
]
I have the below code which produces the following undesired result:
result = [...data
.reduce((acc, {TemplateCategory, TemplateName, ID}) => {
const group = acc.get(TemplateCategory)
group ? group.options.push(ID, TemplateName) : acc.set(TemplateCategory, {TemplateCategory, "options":[ID, TemplateName]})
return acc
}, new Map)
.values()
]
console.log(result) // undesired result:
[
{
TemplateCategory: 'Category A',
options: [1, 'Template 1', 2, 'Template 2']
},
{
TemplateCategory: 'Category B',
options: [3, 'Template 3']
}
]
I am stuck on trying to convert options to an Array of Objects with value and label as properties. Also im struggling trying to reword TemplateCategory property to label.
My desired result is:
[
{
label: 'Category A',
options: [
{
value: 1,
label: 'Template 1'
},
{
value: 2,
label: 'Template 2'
}
]
},
{
label: 'Category B',
options: [
{
value: 3,
label: 'Template 3'
}
]
}
]
TIA
Like this
const data = [
{
ID: 1,
TemplateName: 'Template 1',
TemplateCategory: 'Category A',
},
{
ID: 2,
TemplateName: 'Template 2',
TemplateCategory: 'Category A',
},
{
ID: 3,
TemplateName: 'Template 3',
TemplateCategory: 'Category B',
},
]
const result = [...data
.reduce((acc, {TemplateCategory, TemplateName, ID}) => {
const group = acc.get(TemplateCategory)
group ? group.options.push({value: ID, label: TemplateName}) : acc.set(TemplateCategory, {label: TemplateCategory, "options":[{value: ID, label: TemplateName}]})
return acc
}, new Map)
.values()
]
console.log(result) // undesired result:
I am trying to add an item into an existing object in an array (index each array):
const dataInput = [
{ title: 'first', description: 'test 1' },
{ title: 'second', description: 'test 1' },
]
This is what I've tried:
dataInput.map((data, index) => {
availableItems.push({'idx':index})
})
This pushes a new object instead of adding the element to the existing first and second.
[
{ title: 'first', description: 'test 1' },
{ title: 'second', description: 'test 1' },
{idx:0},
{idx:1}
]
How could I achieve that? (below is what I need)
[
{ title: 'first', description: 'test 1', idx: 0 },
{ title: 'second', description: 'test 1', idx:1 },
]
You need to add a new attribute at each iteration:
const dataInput = [
{ title: 'first', description: 'test 1' },
{ title: 'second', description: 'test 1' },
];
const res = dataInput.map( (data, index) => ({...data, idx:index}) );
console.log(res);
Another option:
dataInput.forEach((element, index) => (element["idx"] = index));
Another option:
const dataInput= [
{ title: 'first', description: 'test 1' },
{ title: 'second', description: 'test 1' },
]
const result = dataInput.reduce((acc, cur, index) => {
acc.push({...cur, idx: index})
return acc
},[])
console.log(result)
I have two arrays in ReactJS as follows
let document = [
{ text: 'Document 1', value: 'abcd' },
{ text: 'Document 2', value: 'efgh' }
]
let filterTypes = [{ value: 'abcd', id: 1 }]
How to generate the filtered array from this in ReactJS containing just one object with value abcd?
You could use Array.prototype.some() method inside Array.prototype.filter() method. Some method returns a Boolean value if at least one item in the array
passes the test by the given callback function.
const doc = [
{ text: 'Document 1', value: 'abcd' },
{ text: 'Document 2', value: 'efgh' },
];
const filterTypes = [{ value: 'abcd', id: 1 }];
const ret = doc.filter((x) => filterTypes.some((y) => y.value === x.value));
console.log(ret);
const docArray = [
{ text: 'Document 1', value: 'abcd' },
{ text: 'Document 2', value: 'efgh' },
];
const filterTypes = [{ value: 'abcd', id: 1 }];
console.log('-----non-matched----');
const nonmatched = docArray.filter(doc => filterTypes.findIndex(filt=> filt.value === doc.value));
console.log(nonmatched);
console.log('------matched----');
const matched = docArray.filter(doc => filterTypes.findIndex(filt=> filt.value !== doc.value));
console.log(matched);
I am new to Quasar and Vue. Could someone explain to me how to solve my task?
Briefly about the task:
(1) I have a q-tree element which represents the folder structure at the left side of a screen [ref.1]
(2) Here is a folder structure [ref.2]
(3) When the user clicks on any element in this folder structure, then he will see a new component on the right side with all children elements of clicked one in a grid layout.
This is what do I have now.
[ref.1] treeComponent.vue
<template>
<q-tree
:nodes="documents"
#click="getId"
node-key="id" >
</q-tree>
</template>
<script>
var documents = require('./documents')
module.exports = {
data: function () {
return {
selectedDoc: x,
documents: documents
}
},
methods: {
getId: function () {
const x = this.getNodeByKey('id')
consol.log(x)
}
}
}
</script>
[ref.2] documents.js
module.exports = [
{
id: '1',
label: 'My Documents',
icon: 'folder',
children: [
{
id: '01',
label: 'Dir 1',
children: [
{ id: '0001', label: 'Doc 1'},
{ id: '0002', label: 'Doc 2'}
]
},
{
id: '02',
label: 'Dir 2',
children: [
{ id: '0003', label: 'Doc 3'},
{ id: '0004', label: 'Doc 4'}
]
},
{
id: '103',
label: 'Dir 3',
children: [
{ id: '0005', label: 'Doc 5'},
{ id: '0006', label: 'Doc 6'},
{ id: '0007', label: 'Doc 7'}
]
}
]
}
]
you need to replace id by key.after this add this handler for each node
handler: (node) => this.onclick(node)
then add this method in methods
onclick(node) {
alert(node.key)
},
this will display id of perticular node
So, the main problem was related to not good enough acquainted with Quasar framework.
Here is my answer to this question:
<template>
<button v-on:click = "showNodeSelected">showClickedNode</button>
<q-tree
:nodes = "documents"
:selected.sync = "selected"
node-key="id"
/>
</template>
<script>
var documents = require('./documents')
module.exports = {
data: function () {
return {
selected: null,
documents: documents
}
},
methods: {
showNodeSelected: function () {
console.log(this.selected)
}
}
}
</script>