How to replace array element by specific index number in react - javascript

I have an array of [false,true,false,false,true] I want to change these boolean values based on index number . I tried my best but didn't find a solution to resolve this issue. I want to be done this in react .
this.setState({
stateValue:[...array,array[0]=true]
})
I tried with this solution but it can add value at the end of array

I would do something like:
this.setState(prevState => {
const newStateValue = [...prevState.stateValue]
newStateValue[desired_index] = boolean_value
return {
...prevState,
stateValue: newStateValue,
}
})
it's good practice when modifying state to do so in an immutable way, by first copying the data, manipulating the new copy and then setting that back into state.
using the prevState => {} callback to setState also helps to ensure the state changes get applied in the correct order if they occur in quick succession

Or this...
var newArray = this.state.statevalue;
newArray[i] = true;
this.setState({stateValue: newArray});

Related

React TS universal hack for inputs with useState doesn't work as expected

I've seen typescript solution with universal handle change function, but in my case it works bad. Need help to understand why this happens.
When I add new product - it adds correctly, but when I try to clear Input manually - first rerender it also changes already added content.
Algorithm to see this mistake: fill inputs, add submit and remove one letter from title input - you'll see that it affects already added title in another state, but only on first rerender
Sandbox: https://codesandbox.io/s/restless-dream-do7bqh?file=/src/App.tsx
The below code-sample may be a solution to achieve the desired objective:
In the handleChange method:
setProduct(prev => ({ ...prev, [name]: value }));
Explanation
The prev value of product is taken as-is
Then, using the ... spread-operator all of prev's props are spread
This is now followed with [name] which is a new prop being added with value as the corresponding value
Any existing prop will be replaced.
Suppose name variable has the value 'title' and value is 'Testing 123', then the product object becomes:
product = {
id: previousValueOfId,
price: previousValueOfPrice,
title: 'Testing 123'
}
Here, previousValueOfId and previousValueOfPrice are supposed to be the values.
Why OP's code did not get the desired result
The code is:
setProduct((prev) => {
(prev as any)[name] = value;
const newValue = { ...prev };
return newValue;
});
This takes the prev product, and changes it's [name] prop.
When prev is changed, it may impact UI elements which are rendered using product.
(NOTE: I am still very new to ReactJS & if this is incorrect kindly update appropriately).

"You may have an infinite update loop in a component render function" when I add this line of code - console.log(perksTree.slots.unshift())

I have a function that find an object from a JSON that has an id === this.match.mainParticipant.stats.perkSubStyle. This object contains a property called slots that is an array and has 4 elements. Each slot has 3 elements which represent runes from a game. If you iterate over the slots and their elements you get this:
I get the object using this function:
secondaryPerks(){
let perksTree = this.$store.state.summonerRunes.find(value => value.id === this.match.mainParticipant.stats.perkSubStyle);
console.log(perksTree.slots.unshift())
return perksTree
}
and I iterate and display the icons using this:
<div v-for='runes in this.secondaryPerks().slots'>
<div v-for='rune in runes.runes'>
<img :src="'https://ddragon.leagueoflegends.com/cdn/img/' + rune.icon" alt="">
</div>
</div>
Now the problem is that because that perks tree is secondary one, the perks in slot[0] can never be picked because if they were picked, they'd have to be part of the primaryPerks tree. This means there's no point displaying that none of them were selected. For that reason I am trying to remove the first slot[0] element from the array, however, when I try to unshift() it, I get an error:
"You may have an infinite update loop in a component render function"
And I have no clue why. Any advices?
Firstly, I think you mean shift rather than unshift. unshift will try to add items to the array rather than removing them. It doesn't actually matter from the perspective of the infinite loop, either method will have the same effect.
You're creating a dependency on the array and then modifying it. Modifying it will trigger a re-render.
Each time the component re-renders it will shift another item onto/out of the array. Even if the call to shift/unshift doesn't actually change anything it will still count as modifying the array.
Try:
computed: {
secondaryPerkSlots () {
const perksTree = this.$store.state.summonerRunes.find(
value => value.id === this.match.mainParticipant.stats.perkSubStyle
);
return perksTree.slots.slice(1)
}
}
with:
<div v-for='runes in secondaryPerkSlots'>
That will create a new array containing the same elements as the original array, omitting the first element.
Alternatively you could put the slice(1) directly in the template:
<div v-for='runes in secondaryPerks().slots.slice(1)'>
Either way I suggest changing the method to a computed property instead. You should also drop the this in your template.
I had the same problem a few months ago.
I think the main issue is that you perform logic such as arr.unshift()(which will cause the template to re-render in this case) in your computed property.
So, imagine this:
const arr1 = [/* ... */];
// This is different
const computedArr = () => {
return arr.filter(() => { /* ... */ });
};
// Than this
const computedArr = () => {
const newArr = arr.filter(() => { /* ... */ });
// Vue cannot allow this without a re-render!
newArr.unshift();
return newArr;
};
The latter will cause the template to re-render;
EDIT
Check the first comment!

resolve field function before sort

I am creating a graphql server using express, and I have a resolver that can transform my fields as per input from the user query.
The transformer that I am using is returning a function, which is the cause of my issues.
I want to sort my result by some user determined field, but since the field is a function, it won't work.
So the resolver looks like this:
const resolver = (req, param) => {
return {
history: async input => {
let size = input.pageSize || 3;
let start = (input.page || 0) * size;
let end = start + size;
let sortField = (input.sort || {}).field || 'timestamp';
return fs.promises.readFile("./history/blitz.json", "utf8").then(data =>
JSON.parse(data)
.slice(start, end)
.map(job => historyTransformer(job))
.sort((a,b) => a[sortField] > b[sortField] ? 1 : a[sortField] < b[sortField] ? -1 : 0)
);
}
};
};
and the transformer:
const historyTransformer = job => {
return {
...job,
timestamp: input =>
dateFormat(job.timestamp, input.format || "mm:hh dd-mm-yyyy")
};
};
I am not sure if I am missing something but is there an easy way of resolving the function call before starting the sorting?
GraphQL fields are resolved in a hierarchal manner, such that the history field has to resolve before any of its child fields (like timestamp) can be resolved. If the child field's resolver transforms the underlying property and your intent is to somehow use that value in the parent resolver (in this case, to do some sorting), that's tricky because you're working against the execution flow.
Because you're working with dates, you should consider whether the format of the field even matters. As a user, if I sort by timestamp, I expect the results to be sorted chronologically. Even if the response is formatted to put the time first, I probably don't want dates with the same times but different years grouped together. Of course, I don't know your business requirements and it still doesn't solve the problem if we're working with something else, like translations, which would cause the same problem.
There's two solutions I can think of:
Update your schema and lift the format argument into the parent field. This is easier to implement, but obviously not as nice as putting the argument on the field it applies to.
Keep the argument where it is, but parse the info parameter passed to the resolver to determine the value of the argument inside the parent resolver. This way, you can keep the argument on the child field, but move the actual formatting logic into the parent resolver.

javascript remove previous filter on an object before applying new one

I have a series of buttons that apply filters to an object using a function similar to this:
isNew(type) {
//need to reset filter first
this.results = this.results.filter(function(type) {
return type.has_user_viewed === true
})
console.log('isNew');
}
The problem I have is if one filter is applied by the user clicking, and then another filter applied, the filtered array is filtered again. What I need to do with the above is reset the object to it's original state before applying a new filter. Not sure how to "reset" a filter here?
Just save the unfiltered data to a variable. filter creates a new array object (shallow copying elements) each time, so your code overwrites the original data with the filtered data:
this.data = [...whatever the source is]
isNew(type) {
//need to reset filter first
this.results = this.data.filter(function(type) {
return type.has_user_viewed === true
})
console.log('isNew');
}
You'll probably need to store the result of the filter somewhere other than this.results if you want to access the original list of results for additional filtering later. Note that this change will likely force you to change other code.
An example of what I'm recommending:
this.filteredResults = this.results.filter(function(type) {
return type.has_user_viewed === true
})
I would suggest that you google immutability, as understanding it will help you greatly when you need to solve similar problems in the future.

ES6 class getter, temporary return or alternative solution

I am trying to solve a problem I am seeing when rendering a list of items in my ui that is coming out of a es6 class I have created. The model is working great, however I am using animations that are listening to (in react) mount, onEnter, and onLeave of the items.
When I apply my filters and sorting via the model and spit back the new list of items via the getter, the animations do not apply to some items because the list is just being re sorted, not necessarily changed.
So my getter just grabs this.products of the class and returns it and applies a sort order to it. And if filters are applied (which are tracked by this._checkedList in the class), the this.products is reduced based on which filters are selected then sorted. So that getter looks like so :
get productList() {
if (this._checkedList.length > 0) {
const filteredProducts = _.reduce(this.filterMap, reduceFilters, []);
const deDuped = _.uniq(filteredProducts, 'id');
return this.applySort(deDuped);
}
const deDuped = _.uniq(this.products, 'id');
return this.applySort(deDuped);
}
What I am trying to figure out, is a way to to temporarily send back an empty array while the filters or sorting run. The reason being the ui would receive an empty array (even if for a split second) and react would register the new sorted/filtered list as a new list and fire the enter/leave/mount animations again.
My attempt was to set a local property of the class like -
this._tempReturn = false;
then in the functions where the sort or filter happen, I set it to true, then back to false when the function is done like this -
toggleFilter(args) {
this._tempReturn = true;
...toggle logic
this._tempReturn = false;
}
Then changed the getter to check for that property before i do anything else, and if it's true, send back an empty array -
get productList() {
if (this._tempReturn) {
return [];
}
...
}
However, this does not seem to work. Even putting a console.log in the if (this._tempReturn) { didn't show any logs.
I also tried sending back a new list with lodash's _.cloneDeep like so :
get productList() {
if (this._checkedList.length > 0) {
const filteredProducts = _.reduce(this.filterMap, reduceFilters, []);
const deDuped = _.uniq(filteredProducts, 'id');
return _.cloneDeep(this.applySort(deDuped));
}
const deDuped = _.uniq(this.products, 'id');
return _.cloneDeep(this.applySort(deDuped));
}
this did not work either. So it seems the empty array return might be a better approach.
I am wondering if there is some way to achieve this - I would like to have the array be return empty for a second perhaps while the filters and sort are applying.
Very stuck on how to achieve, perhaps I am even looking at this problem from the wrong angle and there is a much better way to solve this. Any advice would be welcomed, thanks for reading!
In order to force a re-render of items in a list when updating them you just need to make sure that each items has a unique key property.
Instead of rendering the list, then rendering it as empty, then re-rendering a changed list make sure each child has a unique key. Changing the key property on a child in an array will always cause it to re-render.

Categories