Select an array inside an array in React - arrays

I'm trying to select an arrayfield inside an array. Following code is inserted in my useStore:
const useStore = create(
persist(set => ({
projectszustand: [
{
id: nanoid(),
name: 'Projekt Final 1',
notes: 'Hier sind ein paar Notizen',
begin: '01/01/2001',
end: '02/01/2001',
icon: 'https://www.skopos.de/wp-content/uploads/2021/04/Element-5.svg',
color: 'blue',
edit: false,
selected: true,
feature: [
{
id: nanoid(),
name: 'Feature Final 1',
begin: '01/01/2001',
end: '02/01/2001',
isChecked: false,
edit: false,
selected: false,
todo: [
{...and so on
So, I'm trying to go with forEach and set all selected fields in the feature array to false:
selectFeature: index => {
set(
produce(draft => {
draft.projectszustand[index].feature.forEach(element => {
element.selected = false;
});
draft.projectszustand[index].feature[index].selected =
!draft.projectszustand[index].feature[index].selected;
})
);
},
This no works. Error message is: TypeError: can't access property "feature", draft.projectszustand[index] is undefined
Has somebody an easy solution for this?
Thanks very much for helping.

Related

How to set state in nested array of objects in ReactJs?

I have this object as a state in reactjs. I want to add another object inside the "childoptions: []" array which is nested inside the options array on button click.
How can I achieve this, pls help...
const [select1, setSelect1] = useState({
id: uuid(),
type: 'select',
properties: {
label: 'Select1',
options: [
// {
// id: uuid(),
// optionName: 'red 🔴',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'green 🟢',
// value: '',
// childOptions: [],
// },
// {
// id: uuid(),
// optionName: 'blue 🔵',
// value: '',
// childOptions: [],
// },
],
},
parentId: null,
});
This is achievable by copy the prevState in the new state with the new object inserted inside the options array.
A more detailed explanation could be found at https://stackoverflow.com/a/26254086/9095807
setSelect1((prevState) => {
return {
...prevState,
properties: {
label: 'Select1',
options: prevState.properties.options.map(obj => obj.id === id ? { ...obj, childOptions: [...obj.childOptions, newChildOptions] } : obj),
}
}
})

update an object with useState hook in React

I have an object in board variable
Initial Data:
const [board, setBoard] = useState({
lanes: [],
});
{
lanes: [{
title: 'Bugs',
id: 'd83706b0-b252-11ec-8845-ad6b1e4ecd03',
cards: [{
id: null,
title: 'Bug #1',
description: 'Can AI make memes',
}, ],
},
{
title: 'Tests',
id: 'd83706b0-b252-11ec-8845-ad6b1e4ecd04',
cards: [{
id: null,
title: 'Test #1',
description: 'Can AI make memes',
}, ],
},
],
};
I want to add a new element to the cards array but only to the first element in the lanes array. Other answers seem to point to having to use a callback pattern, but I am quite unfamiliar with this.
Thanks for any help.
As for any modification you want to do on a useState variable, you must use an arrow function inside of the "set" function.
You can do something like that :
setBoard((currentBoard)=> {
currentBoard.lanes[0].cards = [...currentBoard.lanes[0].cards, whateverCardYouWantToAdd ]
return {... currentBoard} //necessary to create a new object because else the hook won't be updated
})
Maybe with this.
const addValue = () => {
let t = [...board];
t[0].lanes.cards.push({
id: null,
title: "Bug #1",
description: "Can AI make memes"
});
};

Why does forEach loop only set the last value if finds to state. ReactJS

const CategoriesData = [
{
name: "Category1",
isActive: true,
children: [
{
name: "Category1Child",
isActive: false,
}
]
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3",
isActive: true,
children: [
{
name: "Category3Child",
isActive: false,
}
]
}
];
const [disabledCategories, setDisabledCategories] = useState([]);
function notActiveCategories(categories) {
// Loop logs out at least 7 isActive: false categories.
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories([...disabledCategories, category]);
console.log(category);
}
});
};
useEffect(() => {
notActiveCategories(CategoriesData);
console.log(disabledCategories); // Only 1 category is in the array.
}, []);
I feel like the function the loop is in calling itself is causing the disabledCategories state to revert to when it was empty and that is leading to only the last step of the foreach to be set.
So how would i get this to loop through the categories array and have the disabledCategories state to contain all of the category objects that have isActive: false.
Which in the example of CategoriesData above, it would mean that the disabledCategories state would contain:
[
{
name: "Category1Child",
isActive: false,
},
{
name: "Category2",
isActive: false,
},
{
name: "Category3Child",
isActive: false,
},
];
Try changing your setDisabledCategories to use the previous state param that comes from setState:
setDisabledCategories(prevState => [...prevState, category])
When multiple setState calls are batched together you need to be careful so they don't override each other. Using this method ensures that your setState calls are "chained" so you always get the updated state.
Way 1: Affect after recursive loop
function notActiveCategoriesRecusive(categories) {
let notActive = []
categories.forEach((category) => {
if (category.isActive) notActive = [...notActive, ...(notActiveCategories(category.children))];
if (!category.isActive) {
notActive.push(category)
}
});
return notActive
};
function notActiveCategories(categories) {
setDisabledCategories(notActiveCategoriesRecusive(categories)
}
Way 2: Get the last state because it doesn't has time to refresh
function notActiveCategories(categories) {
categories.forEach((category) => {
if (category.isActive) notActiveCategories(category.children);
if (!category.isActive) {
setDisabledCategories(oldState => ([...oldState, category]))
}
});
};
I'd only call setState once with the filtered array:
const findInactive = data =>
data.filter(e => !e.isActive)
.concat(...data.filter(e => e.children)
.map(e => findInactive(e.children)))
;
const categoriesData = [ { name: "Category1", isActive: true, children: [ { name: "Category1Child", isActive: false, } ] }, { name: "Category2", isActive: false, }, { name: "Category3", isActive: true, children: [ { name: "Category3Child", isActive: false, } ] } ];
const inactive = findInactive(categoriesData)
// the following is neeeded if it's possible for a
// node to have children and be inactive
// .map(({name, isActive}) => ({name, isActive}))
;
console.log(inactive);
//setDisabledCategories(inactive); // one time in React
This makes the code a lot easier to reason about and decouples React's API out from the filtering logic, which can be moved out to a generic function agnostic of React.
As others have mentioned, if you do want to call setState multiple times as a batch update, you can use the prevState callback to chain the updates: setDisabledCategories(prevState => [...prevState, category]);.

Typescript - Check values in one array are present in another

I have the following array. I am using this array to dynamically produce checkboxes on my UI. This is being used to save user config as to what they will be able to see in a nav menu.
accessLevels: any = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: false
},
{
description: "Jobs",
type: '3',
selected: false
},
{
description: "Calender",
type: '4',
selected: false
}
]
I am making a call to an API which returns me an array of the users config. So what I will get is an array of the pages and their type like this:
{
description: "Equipment",
type: '2'
},
{
description: "Jobs",
type: '3'
}
In the array returned from the API I am just getting the values that should appear checked on the check boxes so what I want to do is loop through the returned array and check if any of the types match any types in the checkbox array if they do I want to set 'selected' to true. Thus checking the checkbox.
Here is what I have so far:
async loadLandlordConfig(key: string) {
const result = await this.userService.loadLandlordConfig(key);
//let accessLevels = [];
this.selectedApiValues = result[0].accessLevels;
this.selectedApiValues.forEach((selectedValue)=> {
});
}
Im not sure how to cross check the values and then change selected to true.
Hope I have made everything clear enough.
Any questions please ask. All help appreciated.
const accessLevels: any[] = [
{
description: 'Properties',
type: '1',
selected: false
},
{
description: 'Equipment',
type: '2',
selected: false
},
{
description: 'Jobs',
type: '3',
selected: false
},
{
description: 'Calender',
type: '4',
selected: false
}];
const results: any[] = [
{
description: 'Equipment',
type: '2'
},
{
description: 'Jobs',
type: '3'
}];
accessLevels.forEach(accessLevel => {
accessLevel.selected = results.some(x => x.type === accessLevel.type); });
For small arrays
You can check for the existence within a filter using some:
const intersect = this.array1.filter(a1 =>
this.array2.some(a2 => a1.type === a2.type));
The problem with this is that you are doing multiple loops through array 2.
For larger arrays
To keep your loops to a constant number, you could create a map of one of your arrays, and check that within a filter of the other array:
const map2 = new Map(this.array2.map(x => [x.type, s]));
const intersect = this.array1.filter(a1 =>
map.has(a1.type));
This adds a little bit of complexity, but is more efficient for all but the simplest cases.
You can achieve what you want with the following simple example :
let accessLevels = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: false
},
{
description: "Jobs",
type: '3',
selected: false
},
{
description: "Calender",
value: '4',
selected: false
}
]
let api = [
{
description: "Equipment",
type: '2'
},
{
description: "Jobs",
value: '3'
}
];
for(var i = 0; i < accessLevels.length; i++) {
accessLevels[i].selected = api.find(e => e.description === accessLevels[i].description) ? true : false;
}
console.log(accessLevels);
You can use Map collection to have O(1) while mapping the second array:
const unique = new Map(received.map(s => [s.description, s]));
const result = accessLevels.map(({ description, type})=>
({selected: (unique.get(description) ? true : false), type, description}))
An example:
let accessLevels = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: false
},
{
description: "Jobs",
type: '3',
selected: false
},
{
description: "Calender",
type: '4',
selected: false
}
]
const received = [
{
description: "Equipment",
type: '2'
},
{
description: "Jobs",
value: '3'
}
];
const unique = new Map(received.map(s => [s.description, s]));
const result = accessLevels.map(({ description, type})=>
({selected: (unique.get(description) ? true : false), type, description}))
console.log(result);
I've made some changes to your code. Here's how it looks like:
async loadLandlordConfig(key: string) {
const result = await this.userService.loadLandlordConfig(key);
// assuming there's always a result.
// ACCESS_LEVELS is the list of your config.
accessLevels = result[0].accessLevels;
this.selectedApiValues = ACCESS_LEVELS.map((al: any) => {
const selected = accessLevels.findIndex(a => a.type === al.type) > -1;
return { ...al, selected }
})
}
This will give you with the value
this.selectedApiValues = [
{
description: "Properties",
type: '1',
selected: false
},
{
description: "Equipment",
type: '2',
selected: true
},
{
description: "Jobs",
type: '3',
selected: true
},
{
description: "Calender",
type: '4',
selected: false
}
]

Filter Array based on a property in the array of its objects

Given is following data structure
const list = [
{
title: 'Section One',
data: [
{
title: 'Ay',
},
{
title: 'Bx',
},
{
title: 'By',
},
{
title: 'Cx',
},
],
},
{
title: 'Section Two',
data: [
{
title: 'Ay',
},
{
title: 'Bx',
},
{
title: 'By',
},
{
title: 'Cx',
},
],
},
];
What i want to do ist to filter this list based on title property in the data array of each object.
An example would be to have the list where the title property of the childs starts with "B", so the list will look like that:
const filteredList = [
{
title: 'Section One',
data: [
{
title: 'Bx',
},
{
title: 'By',
}
],
},
{
title: 'Section Two',
data: [
{
title: 'Bx',
},
{
title: 'By',
}
],
},
];
What i tried so far was something like that:
const items = list.filter(item =>
item.data.find(x => x.title.startsWith('A')),
);
or
const filtered = list.filter(childList => {
childList.data.filter(item => {
if (item.title.startsWith('B')) {
return item;
}
return childList;
});
});
But i think i am missing a major point here, maybe some of you could give me a tip or hint what i am doing wrong
Best regards
Your issue is that you're doing .filter() on list. This will either keep or remove your objects in list. However, in your case, you want to keep all objects in list and instead map them to a new object. To do this you can use .map(). This way you can map your objects in your list array to new objects which contain filtered data arrays. Here's an example of how you might do it:
const list=[{title:"Section One",data:[{title:"Ay"},{title:"Bx"},{title:"By"},{title:"Cx"}]},{title:"Section Two",data:[{title:"Ay"},{title:"Bx"},{title:"By"},{title:"Cx"}]}];
const filterByTitle = (search, arr) =>
arr.map(
({data, ...rest}) => ({
...rest,
data: data.filter(({title}) => title.startsWith(search))
})
);
console.log(filterByTitle('B', list));

Resources