Petite-Vue filtered array reactivity? - javascript

I played arround with Petite-Vue and really like it. Works fine with reactivity / ui updates and helpers like "$el" and "$refs" for easy form field handling / data binding.
Now I tried to have a reactive list with updates live if backend data (gundb) changes.
UI updates work fine if I list all array entries with "v-for".
If I try to filter the array of players by one team (object property "team") the UI is initialy correct rendered, but if a players name changed the update is pushed to gundb and by listener also to players array based on PetiteVue.reative(). So all is fine, but no UI update is triggered. Item is a Proxy as needed for (petite-)vue reactivity.
So it looks like a players.filter(...) call break reactivity. Also tried a method and getter inside of a component and also directly by write players.filter(...) to the html template.
Do I missing the right way how to trigger a re-render of changed filtered reactive array of objects?
I have a function to sync gundb "backend" changes to Petite-Vue.reactive object
gunArray2vue = (gunNode, vueArray) => {
gunNode.map().on((value, soul) => {
item = vueArray.find(item => item && item._ && item._['#'] === soul)
console.log('gunArray2vue', `soul=${soul}`, value, item)
if(!!item && value === null) {
// item in VUE but seems deleted... remove from vue array...
console.log("ToDeleteItem", item)
vueArray.splice(vueArray.indexOf(item), 1)
} else if(!value) {
console.log("Ignore creation of empty object")
return // don't add empty object...
} else if(item) {
//vueArray[vueArray.indexOf(item)] = value // update
console.log("UpdateItem")
item = value // update
} else {
console.log("AddArrayItem")
vueArray.push(value) // add new
}
})
}
Changed Value checked with console:
checked change in js console
But UI isn't refreshed.
Here the code in componente
tested as getter / function
//filteredPlayers: function() {
get filteredPlayers() {
if(this.form.nr || this.form.name) {
return this.players.filter(player =>
player.name.includes(this.form.name) || player.nr == this.form.nr
)
} else {
return this.players
}
}
The filtering is reactive to my input field! But changed name is ignored.
And here is a simpler getter function
get myPlayers() {
page.players
}
But ui isn't updated if an item changes without refresh.
So filtering works reactive, but changed items are not detected and re-rendered.
Also tried directly to use the unfiltered PetiteVue.reactive object like that.
<div v-for="player in page.players">{{player.nr}} {{player.name}}</div>
Rendering works fine, but also not updated on change.
Update
Noticed during my tests today, that updates work if I directly change my original object page.players, but not / no more if I use a reference to the original reactive object?
So I can't use the reactive object by reference without loose ui reactivity for item changes?
Update2
Add and remove an reactive array entry works fine!
Just update an item won't work. Tested different ways to set the item object / key -> val of object. Change isn't reactive.
Function with my tests
gun2arrayListener = (name, table) => {
console.log(`Register Listener Gun table "${name}"`)
DB.get(name).map().on((value, soul) => {
console.log("DEBUG", soul, value)
item = table.find(item => item?._['#'] === soul)
index = table.indexOf(item)
console.log('gun2array', `soul=${soul}`, value, item)
if(Object.is(item, value)) {
console.log("Skip unchanged item")
} else if(item && value === null) {
console.log("ToDeleteItem", item)
table.splice(index, 1)
} else if(!value) {
console.log("Ignore creation of empty object?!")
} else if(item) {
console.log("UPDATE", "ARRAY", item, value)
// !!! CHANGES NOT REACTIVE HERE !!!
// !!! method is triggered, changes looks good, but reload page needed to update UI !!!
//item = value
//TABLES[name][index] = value
//Object.assign(item, value)
//table.splice(index, 1, value)
//table.splice(index, 1); table.push(value)
for (const [key, val] of Object.entries(value)) {
console.log(`${key}: ${val}`)
//item[key] = val
store.players[index][key] = val
console.log(item)
}
} else {
console.log("AddNewGunNodeItem", value)
table.push(value) // add new
}
})
}
Strange... if I test reactivity from outside it works ?!
store.players[0].name = "BUM!" // works as needed in function... ?!
I noticed gun fires the listener twice, but that should result in correct updated array object, just changed to the new value twice...
I can't prevent that, because can't compare old object (vue proxy object) with the new object (plain object without vue proxy) to skip unchanged object here. Object.is results in false because of the Proxy arround...

Related

How to update or add an array in react.js?

I'm using react native and my component is based on class base function. I'm facing difficulty in updating or adding object in an array..
My case :
I have an array:
this.state = {
dayDeatil:[]
}
now i want to add an obj in it but before that i want check if that object exist or not.
obj = { partition :1, day:"sunday","start_time","close_time",full_day:false}
in condition i will check partition and day if they both not match. then add an object if exist then update.
here is function in which i'm trying to do that thing.
setTimeFunc =(time)=>{
try{
console.log("time.stringify() ")
let obj = {
partition:this.state.activePartition,
day:this.state.selectedDay.name,
full_day:false,
start_time:this.state.key==="start_time"?time.toString():null
close_time:this.state.key==="close_time"?time.toString():null
}
let day = this.state.dayDetails.filter((item)=>item.day===obj.day&&item.partition===obj.partition)
if (day.length!==0) {
day[this.state.key]=time.toString()
this.setState({...this.state.dayDetail,day})
} else {
console.log("2")
this.setState({
dayDetails: [...this.state.dayDetails, obj]
})
}
this.setState({ ...this.state, clockVisiblity: false });
}
catch(e){
console.log("error -> ",e)
}
}
To check if the object exists or not, you can use Array.find() method, if it doesn't exists, the method will return undefined.
Now to update the state, the easier way would be to create a new array and set it as the new dayDetails state like this:
const { dayDetails } = this.state;
dayDetails.push(newData);
this.setState({ dayDetails: [...dayDetails] })
You should use the spread operator when setting the state because React uses shallow comparision when comparing states for a component update, so when you do [...dayDetails] you're creating a new reference for the array that will be on the state, and when React compares the oldArray === newArray, it will change the UI.
Also, after your else statement, you're updating the state with the state itself, it's good to remember that React state updates are asynchronous, so they won't be availabe right after the setState function call, this may be causing you bugs too.

How to use a constant in map return function ReactJS

I am wondering how can I use a constant in the map function, basically meaning: I have saved correctly the option I want from my falling menu regarding the constant (I checked it with console.log), for instance I have a name chosen and then I want to use it in the map function but unfortunately I get all the elements undefined when I use the constant; when I replace the constant with a directly written "name", I get all the elements correctly with their names.
Filterhosts=() =>{
var newState = this.state.array.slice(); // in the state array is empty
const selectedOption = this.state.selectedOption;
const writtenOption = this.state.writtenOption;
console.log(selectedOption) //ok
const namearray= this.state.filteredhosts.map(host=> {
return (
host.software.map((sub, subindex) => {
if(selectedOption=="name" || selectedOption=="vendor") {
newState.push(sub.selectedOption) //when I write sub.selectedOption , I receive empty array with all elements as undefined otherwise I become the names of all elements
}
else {
if(sub.vulnerable==true){
newState.push(sub.vulnerability.cve)}
}
})
)
})
const filteredarray = newState.filter( function(item){
return item === writtenOption // here I become properly the searched name//vendor
}
// how to show the whole info for the searched name/vendor(cpe, cve, cvss etc.)
)
console.log(newState); //ok
console.log(filteredarray); //ok
}
Oh I see.
sub.name
is the same as
sub["name"]
which is also the same as
sub[selectedOption]
IF selectedOption is "name". So just use newState.push(sub[selectedOption]) and I think that should work for you.

How to modify a 'value' prop in VueJS before `$emit('input')` finishes updating it

I have a question about creating VueJS components that are usable with v-model which utilise underlying value prop and $emit('input', newVal).
props: {
value: Array
},
methods: {
moveIdToIndex (id, newIndex) {
const newArrayHead = this.value
.slice(0, newIndex)
.filter(_id => _id !== id)
const newArrayTail = this.value
.slice(newIndex)
.filter(_id => _id !== id)
const newArray = [...newArrayHead, id, ...newArrayTail]
return this.updateArray(newArray)
},
updateArray (newArray) {
this.$emit('input', newArray)
}
}
In the above code sample, if I do two modifications in quick succession, they will both be executed onto the "old array" (the non-modified value prop).
moveIdToIndex('a', 4)
moveIdToIndex('b', 2)
In other words, I need to wait for the value to be updated via the $emit('input') in order for the second call to moveIdToIndex to use that already modified array.
Bad solution 1
One workaround is changing updateArray to:
updateArray (newArray) {
return new Promise((resolve, reject) => {
this.$emit('input', newArray)
this.$nextTick(resolve)
})
}
and execute like so:
await moveIdToIndex('a', 4)
moveIdToIndex('b', 2)
But I do not want to do this, because I need to execute this action on an array of Ids and move them all to different locations at the same time. And awaiting would greatly reduce performance.
Bad solution 2
A much better solution I found is to just do this:
updateArray (newArray) {
this.value = newArray
this.$emit('input', newArray)
}
Then I don't need to wait for the $emit to complete at all.
However, in this case, VueJS gives a console error:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
Does anyone have any better solution?
OK. These are your options as far as I understand your use case and application.
First of all, don't mutate the props directly save the props internally and then modify that value.
props: {
value: Array
},
data() {
return {
val: this.value
}
}
If the next modification to the array is dependent on the previous modification to the array you can't perform them simultaneously. But you need it to happen fairly quickly ( i will assume that you want the user to feel that it's happening quickly ). What you can do is perform the modification on the val inside the component and not make it dependent on the prop. The val variable is only initialized when the component is mounted. This way you can modify the data instantly in the UI and let the database update in the background.
In other words, your complete solution would look like this:
props: {
value: Array
},
data () {
return {val: this.value}
},
methods: {
moveIdToIndex (id, newIndex) {
const newArrayHead = this.val
.slice(0, newIndex)
.filter(_id => _id !== id)
const newArrayTail = this.val
.slice(newIndex)
.filter(_id => _id !== id)
const newArray = [...newArrayHead, id, ...newArrayTail]
return this.updateArray(newArray)
},
updateArray (newArray) {
this.val = newArray
this.$emit('input', newArray)
}
}
This solution fixes your problem and allows you to execute moveIdToIndex in quick succession without having to await anything.
Now if the array is used in many places in the application next best thing would be to move it to a store and use it as a single point of truth and update that and use that to update your component. Your state will update quickly not simultaneously and then defer the update to the database for a suitable time.
Emit a message to the parent to change the prop.
Put a watcher on the prop (in the child) and put your code to use the new value there.
This keeps the child from mutating the data it does not own, and allows it to avoid using nextTick. Now your code is asynchronous and reactive, without relying on non-deterministic delays.
How about making copy of the value ?
moveIdToIndex (id, newIndex) {
const valueCopy = [...this.value]
const newArrayHead = this.valueCopy
.slice(0, newIndex)
.filter(_id => _id !== id)
const newArrayTail = this.valueCopy
.slice(newIndex)
.filter(_id => _id !== id)
const newArray = [...newArrayHead, id, ...newArrayTail]
return this.updateArray(newArray)

How to set/update clicked item in React.js?

Could you please tell me how to how to set/update a clicked item in React.js?.
I want the value of the clicked element to change to "test". How can I setup such event handler to do this?
Here is my code
On item click I am trying to update the item like that
btnClick(obj) {
obj.hse = 'test';
console.log(obj);
// this.setState({
// data: [obj]
// });
}
Since your data object is an array, I think the easiest way to implement this is to send your btnClick() function the id of the element that was clicked, update that value, and then save the new state.
Codepen
Like so:
this.state.data.map((item, i) => {
return <li onClick = {
this.btnClick.bind(this, i)
> {
item.hse
} < /li>;
})
By changing map(item) => { to map(item, i) => { you make use of the index parameter of the Array map method. This i variable is then used when binding the btnClick function.
btnClick(id) {
let temp = this.state.data.slice();
temp[id].hse = 'test';
this.setState({
data: temp
});
}
Here, the id is the index of the item clicked. Start off by creating a shallow copy of the this.state.data and put it into a local temp variable. Then, change the hse property of temp[id]. Finally, update the data state with the local variable.
edit: fixed broken codepen link

Assigning an object attribute in JavaScript changes object in a weird way

I'm debugging a complex JS client side framework based on Ext. I stumbled upon a line that gives results that I fail to explain in any way. Here is the line (me is actually just an alias for this):
me.displayTplData = displayTplData;
Some values before executing it:
me.value: "87172981"
displayTplData: Array[1] (this is a local variable)
me.displayTplData: undefined
After the line (F11, "step into next function call"):
me.value: null
displayTplData: Array[1] (stays as before)
me.displayTplData: null
Not only the assignment apparently didn't happen, this also altered value assigned to an unrelated attribute value... The only way I could think of is if displayTplData has an associated setter (similar to descriptors in Python?). But on the other hand, JS debugger doesn't step into any code when executing the line. Also, this framework works on IE8+, so it certainly doesn't use any recent JS developments.
This happens both with FireFox and Chrome, so it must be some "this is supposed to work this way", but I completely don't understand what's going on.
Can someone guess what might be the reason of it? Sorry, I cannot reduce it to a standalone example.
EDIT:
Here is the full function, as a context.
setValue: function(value, doSelect) {
var me = this,
valueNotFoundText = me.valueNotFoundText,
inputEl = me.inputEl,
i, len, record,
dataObj,
matchedRecords = [],
displayTplData = [],
processedValue = [];
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
me.value = value;
me.setHiddenValue(me.value);
return me;
}
// This method processes multi-values, so ensure value is an array.
value = Ext.Array.from(value);
// Loop through values, matching each from the Store, and collecting matched records
for (i = 0, len = value.length; i < len; i++) {
record = value[i];
if (!record || !record.isModel) {
record = me.findRecordByValue(record);
}
// record found, select it.
if (record) {
matchedRecords.push(record);
displayTplData.push(record.data);
processedValue.push(record.get(me.valueField));
}
// record was not found, this could happen because
// store is not loaded or they set a value not in the store
else {
// If we are allowing insertion of values not represented in the Store, then push the value and
// create a fake record data object to push as a display value for use by the displayTpl
if (!me.forceSelection) {
processedValue.push(value[i]);
dataObj = {};
dataObj[me.displayField] = value[i];
displayTplData.push(dataObj);
// TODO: Add config to create new records on selection of a value that has no match in the Store
}
// Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
else if (Ext.isDefined(valueNotFoundText)) {
displayTplData.push(valueNotFoundText);
}
}
}
// Set the value of this field. If we are multiselecting, then that is an array.
me.setHiddenValue(processedValue);
me.value = me.multiSelect ? processedValue : processedValue[0];
if (!Ext.isDefined(me.value)) {
me.value = null;
}
me.displayTplData = displayTplData; //store for getDisplayValue method <------- this is the line
me.lastSelection = me.valueModels = matchedRecords;
if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
inputEl.removeCls(me.emptyCls);
}
// Calculate raw value from the collection of Model data
me.setRawValue(me.getDisplayValue());
me.checkChange();
if (doSelect !== false) {
me.syncSelection();
}
me.applyEmptyText();
return me;
},
Sometimes the debugger provides false information. It is strange that both Firefox's and Chrome's debugger produces the same (wrong) inspection, but if you want to be sure about those values, just put console.log(me.value) before and after the statement, and see what gets printed.

Categories

Resources