React change state in array (for loop) - reactjs

I have a State with flights, and I have a slider to change the max price to change visibility of the flight elements.
maxpriceFilter() {
var flightOffer = this.state.flightOffer;
var sliderPrice = this.state.sliderPrice;
for (var i = 0; i < flightOffer.length; i++) {
if( flightOffer[i].price > sliderPrice) {
this.setState(
{[flightOffer[i].hiddenprice] : true}
);
};
}
This code is adding a undefined field with status true in the root of the state though.. I cant find any best practice on this, other then using computed fields. But I cant get the computed field working either..
Could someone please help me out here?

You don't want to do a setState call in a loop, that will have the react component render multiple times. Build a new state object and call the setState once. You also don't want to filter it out by an if statement, but set previous values to hidden:
maxpriceFilter() {
var flightOffer = this.state.flightOffer;
var sliderPrice = this.state.sliderPrice;
var newState = {};
for (var i = 0; i < flightOffer.length; i++) {
newState[flightOffer[i].hiddenprice] = flightOffer[i].price > sliderPrice;
}
this.setState(newState);
// ...
}
If you're still having issues, it could be that the hiddenprice property isn't what you expect? Might need to post your render() function as well.

Instead of doing your looping when you're updating the state, why not just do the switch on render?
You're probably already looping over all flightOffers in render(). So, just add the check there. Inside your render, pass hiddenPrice={offer.price > sliderPrice} as a prop, or use it directly where you control the visibility.
Why? Because the visibility of a specific item in this case is not state. It is a result of the state.

Related

React props change on setState

Problem: using setList is updating the component props and the state inside the component, not just the state inside the component.
I have worked out a partial solution, cloning the instance works the first time round, and I stopped using splice, see addPhaseToList function at the bottom.
New problem: After cloning the Phase object the list goes back to how it was in props.
In props it had length 5, ['Phase 1','Phase 2','Phase 3','Phase 4','Phase 5'],
Added something ['Phase 1','Phase 2','Phase 3','Phase 4','Test','Phase 5'],
Added something else ['Phase 1','Phase 2','Phase 3','Phase 4','Test 2','Phase 5'].
Console.log reveals after cloning it goes back to. ['Phase 1','Phase 2','Phase 3','Phase 4','Phase 5']
public insertPhase = (phase:string):Phases => {
let clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this)
console.log('clone a', clone)
this.addPhaseToList(phase,clone)
return clone;
private addPhaseToList = (phase:string,phaseObj:Phases) => {
let result:string[] = [];
for (let i=0;i<phaseObj.phaseList.length;i++) {
if (i === phaseObj.phaseList.length-1) {
result = result.concat([phase])
}
result = result.concat([phaseObj.phaseList[i]])
}
phaseObj.phaseList = result;
Any help would be amazing!
Cheers.

How to use ranges saved to React state - Microsoft Word javascript API?

I am using the Microsoft Word Javascript API. I have used the .search() function to retrieve an array of ranges and then have saved them to state.definitions in my App.js React component state. This part works. When I try to print out the state using console.log(JSON.stringify(this.state.definitions)), I see the ranges that I just saved.
In a separate function, I want to retrieve those ranges and highlight them in a new color. This part does not work. I don't get any errors, but I don't see any highlight changes in the document.
Interestingly, if I try to highlight the ranges BEFORE saving them to state, it works. This makes me think that the ranges that I am retrieving from state are not actually the ranges understood by Word.
Any help would be much appreciated.
var flattenedTerms contains an array of range items that were retrieved from Word a few lines above. This code successfully changes the font
for (var i = 0; i < flattenedTerms.length; i++) {
console.log('flattenedTerms: ', flattenedTerms[i]);
flattenedTerms[i].font.color = 'purple';
flattenedTerms[i].font.highlightColor = 'pink';
flattenedTerms[i].font.bold = true;
}
return context.sync().then(function () {
return resolve(flattenedTerms);
})
})
Now the flattenedTerms array, which contains the range items, has been saved to state.definitions using this.setState. This fails to change the font. All of the console.logs do print.
highlightDefinedTerms = () => {
var self = this;
return Word.run(
function (context) {
var definitions = self.state.definitions;
console.log('Highlighting ', definitions.length, ' terms.');
for (var i = 0; i < definitions.length; i++) {
console.log('Highlighting definition: ', JSON.stringify(definitions[i]));
definitions[i].font.color = 'blue';
definitions[i].font.highlightColor = 'red';
definitions[i].font.bold = true;
}
return context.sync();
}
)
}
You need to pass a first parameter to “Word.run” to specify the object whose context you want to resume.
Word.run(self.state.definitions, function(context) ...)

Copying two-dimensional array from state in React.js

I'm attempting to build a small react app to model Conway's Game of Life. I've set up a two-dimensional array to track the state of each cell in a 10-by-10 grid.
I'm attempting to store this array in the State. At each "tick" of the game, I want to make a copy of the array, evaluate each cell, potentially giving it a new value, and then assign the copy back to state. I'm basing this off of the official React Tutorial where they use this exact approach:
handleClick(i) {
//Make a copy from state
const squares = this.state.squares.slice();
//Make some changes to it
squares[i] = 'X';
//Set state to the new value
this.setState({squares: squares});
}
My initial approach was to use slice() as in the example above. Through debugging, I discovered that this didn't work; somehow state is being changed even though I have used various methods to copy it that shouldn't make changes to it. (I understand that if I say var x = this.state.blah and x = 5 that I have changed state because blah is a reference to it)
Here is my code:
doTick = () => {
console.log("doin a tick");
console.log(this.state.squares);
//None of these approaches works
//Three different copy strategies all fail
//const newSquares = Object.assign({}, this.state.squares);
//const newSquares = [...this.state.squares];
//const newSquares = this.state.squares.slice();
const newSquares = this.state.squares.slice();
const origSquares = [...this.state.squares];
//Iterating over the array
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
newSquares[i][j] = evaluateCell(origSquares[i][j], this.countLiveNeighbors(i, j, origSquares));
//evaluateCell(origSquares[i][j], this.countLiveNeighborsAndLog(i, j, origSquares));
}
}
//this.setState({
//squares: newSquares
//});
}
Even though the setState() call is commented out, just having the assignment of newSquares[i][j] = //... is enough to somehow modify state.
Here's the code where I set up the initial array in the constructor for the Board component:
constructor(props) {
super(props);
var array = new Array(10);
for (var i = 0; i < 10; i++) {
array[i] = new Array(10).fill(false);
}
this.state = {
squares: array
};
console.log(this.state.squares);
}
I took a look here but I'm not having any trouble updating the squares based on clicks (that part of my code works fine). Various SO posts and in-person troubleshooters suggested the three different copy strategies that all produce the same problem. I also took a look here.
I'm very new to React and not very skilled in JS generally, and obviously I don't have a good handle on State. Here are my questions:
How can I make a copy of state/part of state/data in state in such a way that the copy is not a reference to state? I want to be able to change this new data without changing state (until I am ready).
Why are the methods used above NOT working properly? The documentation for slice() assures me that I'm getting a copy and not a reference.
Thanks in advance! I'm very confused.
The spread operator only does a shallow copy of the values. This means if you have any nested values in them, they will be referenced rather than copied. For instance:
const a = { field: { innerField: 'test' } };
const b = { ...a } // b === { field: { innerField: 'test' } } SAME field as a
To copy a nested array you should use deep copy methods, such as Lodash's cloneDeep or Ramda's clone
for example, with Lodash's cloneDeep:
const newSquares = _.cloneDeep(this.state.squares);

Detect handle change inside componentDidUpdate method - Reactjs

I have a form that contains several questions. Some of the questions contains a group of subquestions.
The logic to render sub questions is written inside componentDidUpdate method.
componentDidUpdate = (prevProps, prevState, snapshot) => {
if (prevProps !== this.props) {
let questions = this.props.moduleDetails.questions,
sgq = {};
Object.keys(questions).map((qId) => {
sgq[qId] = (this.state.groupedQuestions[qId]) ? this.state.groupedQuestions[qId] : [];
let answerId = this.props.formValues[qId],
questionObj = questions[qId],
groupedQuestions = [];
if(questionObj.has_grouped_questions == 1 && answerId != null && (this.state.groupedQuestions != null)) {
groupedQuestions = questions[qId].question_group[answerId];
let loopCount = this.getLoopCount(groupedQuestions);
for(let i=0; i<loopCount; i++) {
sgq[qId].push(groupedQuestions);
}
}
});
this.setState({groupedQuestions: sgq});
}
}
The problem is that on every key stroke of text field, handleChange method is invoked which will ultimately invoke componentDidUpdate method. So the same question groups gets rendered on every key stroke.
I need a way to detect if the method componentDidUpdate was invoked due to the key press(handleChange) event so that i can write logic as follows.
if(!handleChangeEvent) {
Logic to render question group
}
Any idea on how to integrate this will be appreciated.
I assume your textfield is a controlled component, meaning that its value exists in the state. If this is the case, you could compare the previous value of your textfield to the new one. If the value is different, you know the user entered something. If they are equal however, the user did something else at which point you want your snippet to actually execute.
Basically:
componentDidUpdate = (prevProps) => {
// if value of textfield didn't change:
if (prevProps.textfieldValue === this.props.textfieldValue) {
// your code here
}
}
Another approach is to use componentDidReceiveProps(). There you can compare the props to the previous ones, similarly to the above, and execute your code accordingly. Which method is most suitable depends on how your app works.

Pushing element in a cloned object of state adds value to the state attribute instantaneously

I am trying to publish an updated object but not trying to change the state through the following code in react:
addPlaceToSearch = (place) => {
if (place != this.state.search_text) {
var search = $.extend({}, this.state.search);
if (!search.added_places.includes(place)) {
search.added_places.push(place);
PubSub.publish('searchUpdated', search);
}
}
}
Thus, I am using $.extend({}, this.state.search) to clone the object into another variable, modify that particular variable and then publish it. But when I execute the line 5, and I put a breakpoint on line 6, I can see that the this.state.search is also changed with place being pushed in it's added_places key (which I do not want). How is this happening? And how can I prevent it? I have also tried Object.assign({}, this.state.search) instead of $.extend({}, this.state.search).
EDIT
I even tried the most trivial solution, here it is:
addPlaceToSearch = (place) => {
if (place != this.state.search_text) {
if (!this.state.search.added_places.includes(place)) {
var xyz = {};
for (var key in this.state.search) {
xyz[key] = this.state.search[key];
}
xyz.added_places.push(place);
PubSub.publish('searchUpdated', xyz);
}
}
}
Still, the xyz.added_places.push(place) line changes my this.state.search object too! Kindly help me out.
Finally, after two hours of hard work, I figured out the solution by making a deep copy of the original array. I will read about the differences between shallow and deep copy as given in this answer, till then here is the solution:
var xyz = $.extend(true, {}, this.state.search);
xyz.added_places.push(place);
You can do this better without jQuery using Object.assign()
var xyz = Object.assign({}, this.state.search)
xyz.added_places.push(place)

Resources