React: trying to check/uncheck checkbox - javascript

I'm trying to make an obligatory Todo app to help with my React learning . The behavior I'm going for is multiple Todo lists, where you select a todo name and the list of todo items for that show. Select a different todo name and it's list todo items show, etc (like wunderlist/msft todo). For now it's using static json where each item has a child array.
I'm trying to check/uncheck a checkbox in order to mark the todo as done. My problem is that when a click the checkbox it doesn't update until a click away and then click back. What do I need to do to get it to update immediately?
code sandbox: https://codesandbox.io/s/github/cdubone/todo-react-bootstrap?file=/src/App.js
Here's the relevant code:
The function:
const updateChecked = todo => {
todo.IsChecked = !todo.IsChecked;
};
Properties on the component:
onChange={() => updateChecked(todo)}
isChecked={todo.IsChecked}
The input in the component:
<input
type="checkbox"
id={props.id}
name={props.id}
value={props.title}
checked={props.isChecked}
onChange={props.onChange}
/>
Here's the data -
JSON:
const TodoData = [
{
"Id": 1,
"Title": "Groceries",
"TodoList": [
{
"Id": 1,
"Title": "Apples",
"IsChecked": false
},
{
"Id": 2,
"Title": "Oranges",
"IsChecked": false
},
{
"Id": 3,
"Title": "Bananas",
"IsChecked": true
}
]
},
{
"Id": 2,
"Title": "Daily Tasks",
"TodoList": [{
"Id": 11,
"Title": "Clean Kitchen",
"IsChecked": false
},
{
"Id": 12,
"Title": "Feed Pets",
"IsChecked": false
},
{
"Id": 13,
"Title": "Do Stuff",
"IsChecked": false
}]
},
{
"Id": 3,
"Title": "Hardware Store",
"TodoList": []
},
{
"Id": 4,
"Title": "Costco",
"TodoList": [{
"Id": 21,
"Title": "Diapers",
"IsChecked": false
},
{
"Id": 22,
"Title": "Cat Food",
"IsChecked": false
},
{
"Id": 23,
"Title": "Apples",
"IsChecked": false
},
{
"Id": 24,
"Title": "Bananas",
"IsChecked": false
}]
},
{
"Id": 5,
"Title": "Work",
"TodoList": [
{
"Id": 34,
"Title": "TPS Reports",
"IsChecked": true
}
]
}
]
export default TodoData;

You're mutating state directly rather than using setTodoData to update (a lot of your functions are doing this). Also, you're mapping the the todo items of off ActiveTodoList rather than the todoData which creates a big issue. In react there should only be 1 source of truth, so it would be better to instead store the index of the active list and map "todoData[activeIndex].TodoList.", than to store an instance of the active list itself. Finally, because the data is so nested, you need the list index and the todo item index passed into your update function to reference its location in todoData. Map lets you pass index as a second parameter.
Something along the lines of the below:
todoData[activeIndex].TodoList.map((todo, todoIndex) => {
return (
<div onClick={()=> yourFunction(todo, todoIndex, activeIndex)}> </div>
)
}
yourFunction = (todo, todoIndex, listIndex) => {
let newTodo = {...todo, IsChecked: !todo.IsCheck};
setTodoData((todoData) => [
...todoData.slice(0,listIndex),
{
...todoData[listIndex],
TodoList: [
...todoData[listIndex].Todolist.slice(0,todoIndex)
,newTodo,
...todoData[listIndex].Todolist.slice(todoIndex + 1)
]
},
...todoData.slice(listIndex + 1)
]
}
The goal is not to change things directly, but make copies to send to state. Unfortunately with a nested object it can get confusing very fast.

Related

Adding data to a nested JSON object with children based on an array - Javascript/REACT

Hello stackoverflow community! I've been creating my own fullstack application for a while now, on the NEXTjs framework. This is going pretty well!! Unfortunately, I got stuck on a JSON import object for a treeview component. The treeview component must be populated using a specific nested structure, along with which treeview item should be selected on an initial render.
I managed to get the correct JSON object from the database, using a sql recursive tree function.
const jsonObject =
{
"id": "bfa3fdf8-4672-404e-baf5-0f9098a5705b",
"label": "main category 1",
"children": [
{
"id": "12e544bc-91b1-4e5d-bdbc-2163a5618305",
"label": "sub category 1.1",
"children": []
},
{
"id": "3f5e5cc7-f8b2-4d75-89e1-841c66d863e6",
"label": "sub category 1.2",
"children": [
{
"id": "903a727f-d94d-44ff-b2f6-a985fd167343",
"label": "sub category 1.2.1",
"children": []
},
{
"id": "fb344480-8588-4ce3-9662-f8e89069e4b4",
"label": "sub category 1.2.2",
"children": []
}
]
}
]
}
The problem is that this object, with categories needs to be updated with a 'checked: "true"' or 'checked: "false"' key value pair based on the existence in the referenceSelectedCategories array. And I don't know how to do this; maintaining the structure and object as needed.
const desiredOutputJsonObject =
{
"id": "bfa3fdf8-4672-404e-baf5-0f9098a5705b",
"label": "main category 1",
** "checked": "false",**
"children": [
{
"id": "12e544bc-91b1-4e5d-bdbc-2163a5618305",
"label": "sub category 1.1",
** "checked": "true",**
"children": []
},
{
"id": "3f5e5cc7-f8b2-4d75-89e1-841c66d863e6",
"label": "sub category 1.2",
** "checked": "false",**
"children": [
{
"id": "903a727f-d94d-44ff-b2f6-a985fd167343",
"label": "sub category 1.2.1",
** "checked": "false",**
"children": []
},
{
"id": "fb344480-8588-4ce3-9662-f8e89069e4b4",
"label": "sub category 1.2.2",
** "checked": "true",**
"children": []
}
]
}
]
}
const referenceSelectedCategories =
[
{
"categoryId": "12e544bc-91b1-4e5d-bdbc-2163a5618305",
"productId": "efed1c38-391b-4b5a-a9f1-91f3faec5f44",
"Id": "f82b0f63-3f39-486c-9157-5c7683b8e3b2"
},
{
"categoryId": "fb344480-8588-4ce3-9662-f8e89069e4b4",
"productId": "efed1c38-391b-4b5a-a9f1-91f3faec5f44",
"Id": "b2e8681b-eec4-404d-8f87-c6314db42e30"
}
]
I've read several stackoverflow questions, also searched for examples, but can't get it to work. Could someone help me out here?
Some extra information:
Code language I'm using is REACT on NEXTjs framework;
Treeview component could have a dept of max 5 levels;
The structure of the JSON object doesn't change, it's exactly as presented above.
The "id" in the JSON object corresponds to the "categoryId" in the array.
Do you need more information? :) Just ask, I'll provide you with the extra details!
Kind Regards,
Chris
A straight forward solution with recursive method. Done a quick test, working fine. If any issues found, please point it out.
const parentObj =
[
{
"categoryId": "12e544bc-91b1-4e5d-bdbc-2163a5618305",
"productId": "efed1c38-391b-4b5a-a9f1-91f3faec5f44",
"Id": "f82b0f63-3f39-486c-9157-5c7683b8e3b2"
},
{
"categoryId": "fb344480-8588-4ce3-9662-f8e89069e4b4",
"productId": "efed1c38-391b-4b5a-a9f1-91f3faec5f44",
"Id": "b2e8681b-eec4-404d-8f87-c6314db42e30"
}
]
const existingId = parentObj.map((item)=> (item.Id))
const childobj =
{
"id": "bfa3fdf8-4672-404e-baf5-0f9098a5705b",
"label": "main category 1",
"children": [
{
"id": "12e544bc-91b1-4e5d-bdbc-2163a5618305",
"label": "sub category 1.1",
"children": []
},
{
"id": "3f5e5cc7-f8b2-4d75-89e1-841c66d863e6",
"label": "sub category 1.2",
"children": [
{
"id": "903a727f-d94d-44ff-b2f6-a985fd167343",
"label": "sub category 1.2.1",
"children": []
},
{
"id": "f82b0f63-3f39-486c-9157-5c7683b8e3b2",
"label": "sub category 1.2.2",
"children": []
}
]
}
]
}
const childObj = [childobj]
const updateData=(obj)=> {
if(existingId.includes(obj.id)) obj['checked'] = true; else obj['checked'] = false
}
const traverse=(childObj)=> {
for(const obj of childObj) {
updateData(obj);
if(obj.children.length > 0) traverse(obj.children);
}
}
traverse(childObj);
Here you can ty this logic :
let desiredOutputJsonObject = {
id: "bfa3fdf8-4672-404e-baf5-0f9098a5705b",
label: "main category 1",
checked: false,
children: [
{
id: "12e544bc-91b1-4e5d-bdbc-2163a5618305",
label: "sub category 1.1",
checked: true,
children: [],
},
{
id: "3f5e5cc7-f8b2-4d75-89e1-841c66d863e6",
label: "sub category 1.2",
checked: false,
children: [
{
id: "903a727f-d94d-44ff-b2f6-a985fd167343",
label: "sub category 1.2.1",
checked: false,
children: [],
},
{
id: "fb344480-8588-4ce3-9662-f8e89069e4b4",
label: "sub category 1.2.2",
checked: true,
children: [
{
id: "fb344480-8588-4ce3-9662-f8e89069e4b9",
label: "sub category 1.2.2",
checked: false,
children: [],
},
],
},
],
},
],
};
let referenceSelectedCategories = [
{
categoryId: "12e544bc-91b1-4e5d-bdbc-2163a5618305",
productId: "efed1c38-391b-4b5a-a9f1-91f3faec5f44",
Id: "f82b0f63-3f39-486c-9157-5c7683b8e3b2",
},
{
categoryId: "fb344480-8588-4ce3-9662-f8e89069e4b4",
productId: "efed1c38-391b-4b5a-a9f1-91f3faec5f44",
Id: "b2e8681b-eec4-404d-8f87-c6314db42e30",
},
{
categoryId:"fb344480-8588-4ce3-9662-f8e89069e4b9",
productId: "efed1c38-391b-4b5a-a9f1-91f3faec5f44",
Id: "b2e8681b-eec4-404d-8f87-c6314db42e30",
},
];
let stack = [desiredOutputJsonObject];
while (stack.length) {
let desiredOutput = stack.pop();
if (desiredOutput.children) {
desiredOutput.children.forEach((node) => {
//get node whose id == category id
let result = referenceSelectedCategories.find(
(obj) => obj.categoryId === node.id
);
// while traversing if we get referenceSelectedCategories.categoryId ==desiredOutputJsonObject.id
if (result) {
node.checked = true;
}
// for traverse purpose
if (node.children.length) {
stack.push(node);
}
});
}
}
console.log(desiredOutputJsonObject);

How to remove items from an array of multidimensional arrays

I need some help on how to remove items from a TreeView (it's a Vue.js project), the TreeView is build based on an element like that:
[
{
"id": 1,
"name": "COMERCIALIZAÇÃO",
"idp": "",
"children": [
{
"id": 5,
"name": "Pasta 1",
"idp": 1,
"children": [
{
"id": 6,
"name": "Pasta 1 2",
"idp": 5,
"children": [
{
"id": 7,
"name": "NO.FT.DRC.01.00.001.pdf",
"file": "pdf",
"idp": 6
},
{
"id": 8,
"name": "PR.FT.DRC.01.00.003.pdf",
"file": "pdf",
"idp": 6
}
]
},
{
"id": 9,
"name": "imprimir p luiza.pdf",
"file": "pdf",
"idp": 5
},
{
"id": 66,
"name": "Pasta 1 3",
"idp": 5,
"children": [
{
"id": 77,
"name": "NO.FT.DRC.01.00.001.pdf",
"file": "pdf",
"idp": 66
},
{
"id": 88,
"name": "PR.FT.DRC.01.00.003.pdf",
"file": "pdf",
"idp": 66
}
]
}
]
},
{
"id": 10,
"name": "Backend.docx",
"file": "pdf",
"idp": 1
},
{
"id": 0,
"name": "DT.DC.RPI.03.03.1235_V2.docx",
"file": "pdf",
"idp": 1
}
]
},
{
"id": 2,
"name": "DISTRIBUIÇÃO",
"idp": "",
"children": [
{
"id": 11,
"name": "Pasta 2",
"idp": 2,
"children": [
{
"id": 12,
"name": "pasta 2 1",
"idp": 11,
"children": [
{
"id": 13,
"name": "script.sql",
"file": "pdf",
"idp": 12
}
]
}
]
}
]
},
{
"id": 3,
"name": "GERAÇÃO",
"idp": "",
"children": [
{
"id": 14,
"name": "Pasta 3",
"idp": 3
}
]
},
{
"id": 4,
"name": "SERVIÇOS",
"idp": "",
"children": [
{
"id": 5,
"name": "teste",
"idp": 4
}
]
}
]
I'm not sure, but I think that the best way to describe that element is: array of mutidimensional arrays, right?
I've created a CodePen to show the closest I got when using recursivity, but surely mine isn't the best solution since it doesn't work on every delete. Take a look at my code: https://codepen.io/luizarusso/pen/zYxLOPb?editors=1010
for (let i = 0; i < items.length; i++) {
if (items[i].id == item.id) {
//se achou o cara que vai ser removido, chama a função de remover
return this.removeItem(i);
} else {
if (items[i].children) {
if (items[i].idp == "") {
this.caminho = [];
}
this.caminho.push(i);
this.delFile(item, items[i].children);
} else {
if (items.length == 1 + i) {
this.caminho.pop();
}
}
}
}
Any ideas? Feel free to optimize my code directly on CodePen if you prefer :)
EDIT: Just to clarify, my problem here is strictly on how to remove an element by the id. When the user clicks on the bin icon I know what element I need to remove, but I don't know how to take it off of the array. Map, Filter and other native JS functions cannot do that to an array of arrays/JSON, so I tought about using recursivity or something else to make it work.
You need to look at objects, not just arrays.
Let me recommend an example library. https://github.com/leezng/vue-json-pretty.
If your question about multidimensional array iteration and process i think you have to ask on javascript and/or algorithm tags.
I hope this answer will help you.
The problem was with where I placed the this.caminho.pop()
I should only do that in the "else" of the condition that compares the id of the current item with the id of the item I'm looking for.
delFile(item, items) {
for (let i = 0; i < items.length; i++) {
if (items[i].id == item.id) {
//if the current item has the same id as the item I'm looking for
//it means I found the guy and I call the function to remove it
return this.removeItem(i);
} else {
//otherwise, I keep on searching
if (items[i].children) {
//if the item on the actual index have children, I'll search among them
if (items[i].idp == "") {
//if the items doesn't have a parent, I clean the "caminho" (path) var. That var traces the route till the item I'm looking for
this.caminho = [];
}
//I push the index to the var that traces the route
this.caminho.push(i);
//I call the function back again, now with the child items
this.delFile(item, items[i].children);
}
if (items.length == 1 + i) {
//if the item's lenght has been completely coursed, I pop the index out of the var that holds the route, because at this point I know the item I'm looking for is not among them
this.caminho.pop()
}
}
}
},
Here is the solution: https://codepen.io/luizarusso/pen/zYxLOPb
Works with treeview with any deepness

Parsing categories tree into HTML select tag

I have this categories tree input :
"categories": [
{
"text": "Upstate",
"id": 3,
"category_parent_id": 0,
"children": []
},
{
"text": "North",
"id": 2,
"category_parent_id": 0,
"children": [
{
"text": "Child N 1",
"id": 5,
"category_parent_id": 2,
"children": [
{
"text": "Another Child 1",
"id": 11,
"category_parent_id": 5,
"children": []
},
{
"text": "Another Child 2",
"id": 10,
"category_parent_id": 5,
"children": []
}
]
},
{
"text": "Activity",
"id": 4,
"category_parent_id": 2,
"children": []
}
]
},
{
"text": "Health",
"id": 1,
"category_parent_id": 0,
"children": [
{
"text": "Good Health",
"id": 9,
"category_parent_id": 1,
"children": []
},
{
"text": "Bad Health",
"id": 8,
"category_parent_id": 1,
"children": []
}
]
}
]
So, now I want to populate my select box like this :
Upstate
North
-Child N 1
--Another Child 1
--Another Child 2
-Activity
Health
-Good Health
-Bad Health
So, how can I parse through the input tree and populate the select box with these values? Any algorithm or recursive function approach I can use to achieve this ?
make a recursive function
flatCategories(data: any[], children: any[], index: number) {
data=data||[];
children.forEach(x => {
data.push({ id: x.id, text: '-'.repeat(index) + x.text });
if (x.children && x.children.length)
this.flatCategories(data, x.children, index + 1)
})
return data
}
You can use like
let dataFlat=this.flatCategories([], this.data.categories, 0);
console.log(this.dataflat.map(x => x.text))
If you want to create a recursive component it's easy (but in case of select not work)
#Component({
selector: 'item-line',
template: `
<div *ngFor="let item of children" [style.margin-left]="index+'rem'">
{{item.text}}
<item-line *ngIf="item.children" [children]="item.children" [index]="index+1">
</item-line>
</div>
`,
})
export class HelloComponent {
#Input() children:any[]
#Input() index:number=0;
}
You can see in stackblitz

Javascript - grab value inside of an object nested within an object

I am trying to grab a value of a key inside of an object in an array which itself is an object in an array.
Here is what it looks like:
var books = [
{
"title": "title1",
"author": "author1",
"users": [
{
"id": 1,
"name": "Isidro"
},
{
"id": 4,
"name": "Jose Miguel"
},
{
"id": 3,
"name": "Trinidad"
}
]
},
{
"title": "title2",
"author": "author2",
"users": [
{
"id": 4,
"name": "Jose Miguel"
},
{
"id": 5,
"name": "Beatriz"
},
{
"id": 6,
"name": "Rosario"
}
]
},
What I am trying to do, 2 things:
First:
when I click on a user name in the HTML, I want to match the name clicked with the same user name in all the objects it is present in.
Second:
display the title of the books this user name is present in.
For example: when I click on Jose Miguel I want to see the 2 books he has read.
At the moment I have this:
var btnUser = document.querySelectorAll(".individualUsers");
for (var i = 0; i < btnUser.length; i++) {
btnUser[i].addEventListener("click", function() {
var clickedUser = this.innerText
var userBooks = books
.filter(x => x.users.name.indexOf(clickedUser) > -1)
.map(x => ` <li>${x.title}</li> <li>${x.author}</li>`);
console.log(clickedUser);
});
}
My problem is x.users.name.indexOf(clickedUser)is not accessing the user name.
You need to search inside the users array as well, one neat way is to do so with Array.some that return true if some of the conditional is true.
const books = [{
"title": "title1",
"author": "author1",
"users": [{
"id": 1,
"name": "Isidro"
},
{
"id": 4,
"name": "Jose Miguel"
},
{
"id": 3,
"name": "Trinidad"
}
]
},
{
"title": "title2",
"author": "author2",
"users": [{
"id": 4,
"name": "Jose Miguel"
},
{
"id": 5,
"name": "Beatriz"
},
{
"id": 6,
"name": "Rosario"
}
]
}
];
const clickedUser = 'Jose Miguel';
var userBooks = books
.filter(x => x.users.some(user => user.name.indexOf(clickedUser) > -1));
console.log(userBooks);

Angular UI Tree - Allow only drag & drop into second level (children nodes)

I am currently using Angular UI Tree.
My object:
[
{
"id": 1,
"title": "class1",
"students": [
{
"id": 11,
"title": "student1.1",
},
{
"id": 12,
"title": "student1.2"
}
]
},
{
"id": 2,
"title": "class2",
"students": []
},
{
"id": 3,
"title": "class3",
"students": [
{
"id": 31,
"title": "student3.1"
}
]
}
]
What I want to achieve is to allow students to drag & drop inside classes (The classes do not have to be draggable, and students do not have to be dropped into first level (classes) only to a second level (students)).
Is this possible to achieve with Angular UI Tree?
I finally made It like this:
I check in the destination Scope that parent is still a uiTreeNode.
In controller:
$scope.treeOptions = {
accept: function(sourceNodeScope, destNodesScope, destIndex) {
if (destNodesScope.$parent.$type === "uiTreeNode"){
return true;
}else{
return false;
}
}
};
and in view:
<div ui-tree="treeOptions">

Categories