React List Render Order Must Be Consistent? - javascript

I really don't know how to explain this problem, but I have the fiddles to help:
Only line 25 gets changed
CORRECT: http://jsfiddle.net/0maphg47/5/
var ListAnimate = React.createClass({
getInitialState: function() {
return {
list: [
{id: 1, caption: "Hello"},
{id: 2, caption: "There"},
{id: 3, caption: "Whatsup"},
{id: 4, caption: "Sanket"},
{id: 5, caption: "Sahu"}
]
};
},
shuffle: function() {
this.setState({ list: this.state.list.shuffle() });
},
render: function() {
// create a sorted version of the list
var sortedCopy = this.state.list.slice().sort(function(a, b) {
return a.id - b.id;
});
return <div>
<button onClick={this.shuffle}>Shuffle</button>
<ul>
{sortedCopy.map(function(el, i) {
// find the position of the element in the shuffled list
var pos = this.state.list.indexOf(el);
return <li key={el.id} style={ {top: (pos*60)+'px'} }>
{el.caption} {el.id}
</li>;
}, this)}
</ul>
</div>;
}
});
React.render(<ListAnimate />, document.body);
WRONG: http://jsfiddle.net/0maphg47/6/
var ListAnimate = React.createClass({
getInitialState: function() {
return {
list: [
{id: 1, caption: "Hello"},
{id: 2, caption: "There"},
{id: 3, caption: "Whatsup"},
{id: 4, caption: "Sanket"},
{id: 5, caption: "Sahu"}
]
};
},
shuffle: function() {
this.setState({ list: this.state.list.shuffle() });
},
render: function() {
// create a sorted version of the list
var sortedCopy = this.state.list.slice().sort(function(a, b) {
return a.id - b.id;
});
return <div>
<button onClick={this.shuffle}>Shuffle</button>
<ul>
{this.state.list.map(function(el, i) {
// find the position of the element in the shuffled list
var pos = this.state.list.indexOf(el);
return <li key={el.id} style={ {top: (pos*60)+'px'} }>
{el.caption} {el.id}
</li>;
}, this)}
</ul>
</div>;
}
});
React.render(<ListAnimate />, document.body);
Why do we have to render the li objects in the same order every time if the key can determine uniqueness? I don't get why the order of the li elements matter, but I'm probably missing something obvious

Take a look in the Elements view of your browser's debugger to see what is happening to the DOM when you click on the Shuffle button.
In the first (correct) case, the only thing that changes in the DOM is the style attribute of each list item. The order and contents of the items don't change, only the appearance of the order changes. The element of key X was remains in the same position before and after the shuffle, so new DOM elements do not need to be created.
In the second (wrong) case, the actual order of the elements is shuffled. While key 1 may be in first position before the shuffle, it may in the fourth position after the shuffle. The attributes of the items are not being updated in place; rather, React may be creating new items where an item has changed positions. Hence, this can have an unpredictable effect on your transitions.

Related

Recursive function to create a list from another deep list

Another question about recursive function, I cant get my head arround them.
I have a list with groups that can have any depth, an example:
{
Id: 1,
Name:"Root",
Children: [
{
Id: 1,
Name:"",
Children: [
{
Id: 1,
Name:"",
Children: [
{
Id: 1,
Name:"",
Children: []
},
]
},
]
},
{
Id: 2,
Name:"",
Children: []
},
{
Id: 3,
Name:"",
Children: []
},
]
}
I show these groups in a dropdown that the user can select.
What I need to do is when the user clicks on any group, I need to show all users that are a part of that group AND its subgroups.
The information about which users belong to the group and its subgroups is hold by the userlist. That list is flat and every user has an prop that contains an membership array.
I have re-written this method below several times, this is the closest I get, but this more than doubles the expected lenght because I get dublicates.
const getAllUsersInGroup = (group, usersFiltered) => {
if (!group.Children.length) return usersFiltered.flat();
return group.Children.flatMap((g) => {
return getAllUsersInGroup(
g,
[...usersFiltered, users.filter((u) => u.Memberships.some((m) => m.GroupId === g.Id))]
);
});
};
Another test returns almost all but there is missing users on bigger groups with many subgroups.
const getAllUsersInGroup = (group, userss) => {
if (!group.Children.length) return [...userss].flat();
return group.Children.flatMap((g) => {
return getAllUsersInGroup(g,
users.filter((u) => u.Memberships.some((m) => m.GroupId === g.Id)),
);
});
};
I must be stuck in some wrong thinking or just pure stupid..
Maybe I dont need to check the Children lenght and just go thro them all, but as I understand it you need some statment that stops the method.
A little help would be much appreciated!
Regards

I am trying to delete multiple elements from an array through checkbox i am using vue.js but i am unable to figure out how to do it

i have a scenario where I am triggering an event on button click after selecting elements from the grid when the button is clicked I need to remove that element from the current modal/grid
CheckedNames:["a","b","c"],
CheckedNamesId:[1,2,3],
DeletefromArray(){
this.CheckedNames.forEach(element => {
this.deleteItem(this.CheckedNamesId,this.CheckedNamesId.length);
});
},
deleteItem(index,length) {
this.List.splice(index, length)
},
check: function(e,row) {
this.CheckedNamesId.push(row.id)
console.log(this.CheckedNamesId)
},
Now if I select "a","b","c" by the check box, I need to remove it from the array,
If I understood you correctly try something like following snippet:
new Vue({
el: '#demo',
data() {
return {
names: [{id: 1, name: 'a'},{id: 2, name: 'b'}, {id: 3, name: 'c'}, {id: 4, name: 'd'}, {id: 5, name: 'e'}, {id: 6, name: 'f'}, {id: 7, name: 'g'}],
checkedNames: []
}
},
methods: {
check(item) {
if (!this.checkedNames.length || !this.checkedNames.find(f => item.id === f.id)) {
this.checkedNames.push(item)
} else {
this.checkedNames = this.checkedNames.filter(f => item.id !== f.id)
}
},
del() {
this.names = this.names.filter(a => !this.checkedNames.includes(a))
this.checkedNames = []
}
}
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<ul>
<li v-for="item in names" :key="item.id">
{{ item.name }}
<input type="checkbox" #click="check(item)" />
</li>
</ul>
<button #click="del">Delete</button>
</div>
You can try like the code below. When looping in List array check if id of the element is present in checkedNameID array by using indexof. If present add that element's index in a array defined outside loop. Then you can slice it away using another loop from array. When you slice use 1 as second parameter it will remove 1 element at a a time in loop.
const arr = []
deleteFromArray(){
this.List.forEach((element,index) => {
if(this.checkedNameID.indexOf(element.id) !== -1){
this.arr.push(index);
}
});
arr.forEach(item => {
this.List.splice(index,1);
}
},

Remove children From A Nested Array using Recursion

I want to be able to remove an object from an array of objects that have children. I thought immediately this is a job for recursion, but I am unable to get my recursion function to work properly. I thought of using reduce to rebuild the data structure without the object that I want to remove. The function should accept two parameters the array of nested objects and an Id.
My requirements are: remove the node and all children below.
At first glance, this seems easy, but the challenge I find is removing a child and keeping the entire data structure intact. Deleting the parent by filtering based on the id is trivial, but the nested children pose a problem.
My data structure looks like this:
const data = [{
id: 'BFQEA1W2RK1YRETZ9343',
name: 'Cover',
activityId: 'BFQEA1W2RK1YRETZ9343',
nodeType: 'activity',
suppressed: true,
hidden: true
},
{
children: [
{
id: 'ZNRAE749BSD0CTGHY888',
name: 'Consultants, Reviewers, and National Geographic Exploration',
activityId: 'ZNRAE749BSD0CTGHY888',
nodeType: 'activity',
suppressed: false,
hidden: false
},
{
id: 'JZLS37EVZQM22H9Q4655',
name: 'The National Geographic Approach',
activityId: 'JZLS37EVZQM22H9Q4655',
nodeType: 'activity',
suppressed: false,
hidden: false
},
]
}
]
If I pass this Id(ZNRAE749BSD0CTGHY888) to the function my expected data result should be:
const expected = [{
id: 'BFQEA1W2RK1YRETZ9343',
name: 'Cover',
activityId: 'BFQEA1W2RK1YRETZ9343',
nodeType: 'activity',
suppressed: true,
hidden: true
},
{
children: [
{
id: 'JZLS37EVZQM22H9Q4655',
name: 'The National Geographic Approach',
activityId: 'JZLS37EVZQM22H9Q4655',
nodeType: 'activity',
suppressed: false,
hidden: false
},
]
}
]
My function looks like this:
findNode = (id, arr) => {
return arr.reduce((a, item) => {
// if (item.id === id) {
// console.log('here');
// return item;
// }
if (item.id !== id) {
a.push(item);
}
if (item.children) {
return this.findNode(id, item.children);
}
}, []);
};
The function's reduce accumulator is undefined, but I am unsure why. It should be making a new array. What am I missing here?
In my head, this seems to work, but it fails. Maybe my approach is completely off. How should I go about solving this?
In your code you are not returning the accumulator. That's why you're getting undefined. And there's no reason to recurse over children of items that you don't push, so you should nest the recursion under the if.
You can loop over you root array with reduce(). If the id matches, just return and continue. Other wise you can recursively pass the children the filter and push to the return array:
const data = [{id: 'BFQEA1W2RK1YRETZ9343',name: 'Cover',activityId: 'BFQEA1W2RK1YRETZ9343',nodeType: 'activity',suppressed: true,hidden: true},{children: [{id: 'ZNRAE749BSD0CTGHY888',name: 'Consultants, Reviewers, and National Geographic Exploration',activityId: 'ZNRAE749BSD0CTGHY888',nodeType: 'activity',suppressed: false,hidden: false},{id: 'JZLS37EVZQM22H9Q4655',name: 'The National Geographic Approach',activityId: 'JZLS37EVZQM22H9Q4655',nodeType: 'activity',suppressed: false,hidden: false},]}]
function filterID(id, data) {
return data.reduce((arr, item) => {
if (item.id != id) {
if (item.children) item.children = filterID(id, item.children)
arr.push(item)
}
return arr // <<-- need to return the accumulator
}, [])
}
console.log(filterID("ZNRAE749BSD0CTGHY888", data))

How to traverse a list of items in VueJS

Here is my vue instance:
new Vue({
el: '#app',
data: {
showPerson: true,
persons:
[
{id: 1, name: 'Alex'},
{id: 2, name: 'Bob'},
{id: 3, name: 'Chris'}
],
},
methods: {
nextPerson: function(){
this.showPerson = false;
}
}
});
I am trying to walk the persons array of objects. I want the list to start with the first element of the array and below it should be a button which is responsible for hiding the previous element and showing the next element of the array. Once the user reaches the last element, the Next button should not go back to the first element.
Here is the HTML:
<div id="app">
<ul v-for="person in persons">
<li v-if="showPerson">{{person.name}}</li>
</ul>
<button #click="nextPerson">Next Person</button>
</div>
And the JSBin Link. At this moment I can only show and hide the items all at once and not one at a time. How can I implement this?
One of the ways of doing so would be to keep an index for the person being shown on screen. I've named this variable as shownPersonIndex.
Then, you need to show the next person on click of button. So in the click event handler, you need to increment the index by 1. Also, you need to ensure that the index value does not exceed the length of the array. So I've modified the click handler as follows:
nextPerson: function() {
if(this.shownPersonIndex < (this.persons.length - 1)) {
this.shownPersonIndex++;
}
}
Finally, you can either use a computed to display the currently shown person or an inline expression like this.persons[this.shownPersonIndex].name to show the person on screen.
I am using v-if="this.shownPersonIndex != this.persons.length - 1" to hide the "next" button as you reach the last element on the array.
new Vue({
el: '#app',
data: {
shownPersonIndex: 0,
persons: [{
id: 1,
name: 'Alex'
},
{
id: 2,
name: 'Bob'
},
{
id: 3,
name: 'Chris'
}
],
},
methods: {
nextPerson: function() {
if(this.shownPersonIndex < (this.persons.length - 1)) {
this.shownPersonIndex++;
}
}
},
computed: {
shownPerson: function() {
return this.persons[this.shownPersonIndex];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
Person: {{ shownPerson.name }}
<button v-if="this.shownPersonIndex != this.persons.length - 1" #click="nextPerson">Next Person</button>
</div>

Loop to display hierarchical data

I am creating an array out of essentially hierachical data, for example as below:
[
{id: 1, title: 'hello', parent: 0, children: [
{id: 3, title: 'hello', parent: 1, children: [
{id: 4, title: 'hello', parent: 3, children: [
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4}
]},
{id: 7, title: 'hello', parent: 3}
]}
]},
{id: 2, title: 'hello', parent: 0, children: [
{id: 8, title: 'hello', parent: 2}
]}
]
I am looking to loop through the array, but can't get my head around how to recursively loop down to create an unordered list where each child level is indented.
Trying to do this in JavaScript, but need a push in the right direction for the construction of the loop to drill down until there are no more children, and then back up to the top array.
Any help would be appreciated.
I answered a question about this before
Here is demo for it: http://jsfiddle.net/zn2C7/7/
var list = $("<ul>");
function populatedata() {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == -1) {
var item = $("<li>").html(folder.FolderName);
list.append(item);
var children = $('<ul>');
item.append(children);
checkChild(folder.FolderID, children);
}
});
$('body').append(list);
}
function checkChild(parentid, parent) {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == parentid) {
var item = $("<li>").html(folder.FolderName);
var children = $('<ul>');
parent.append(item);
item.append(children);
checkChild(folder.FolderID, children);
}
else {
return ;
}
});
}
It was possible to build it using html variable, like you tried to do that, but it is much simpler to use DOM manipulation functions of jQuery ($('<ul>') and $('<li>') - create new element, .append() - append element to some other element)
function checkChild(parentid) {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == parentid) {
html += '<li><ul>' + folder.FolderName;
checkChild(folder.FolderID);
html+=</ul></li>
return html;
}
else {
return ;
}
});
}
Also, please note that in code above you are doing return html; from each function callback. Not sure what you wanted to get exactly, but in .each it may work like break in regular loop (if you will return false):
We can stop the loop from within the callback function by returning false.
That is from jquery api page.
Also, for such tasks I prefer to use debugger. At this moment there are a lot of powerful tools for HTML/CSS/JS debugging in browser. Just press F12 in Chrome, IE or FF (for the last one you may need to install Firebug extension) and you will get a lot of helpful features along with simple JS debugging.

Categories

Resources