'setState' of useState hook occurs twice by one call - arrays

There is a board with squares their value relies on an array, it is handled with useState hook. Every click should raise the value by one, but unfortunately, it raises it by two (except the first click).
My questions are:
(1) Why is it happen, (2) how to avoid it, and, in general, (3) is there a better way to handle such an array with hooks.
let emptyBoard = Array.from({ length: parseInt(props.rows, 10) }, () =>
new Array(parseInt(props.columns, 10)).fill(0)
);
const [squaresValues, setSquaresValue] = useState(emptyBoard);
function onClick(id) {
const [rowIndex, cellIndex] = id;
console.log("the " + id + " square was clicked");
setSquaresValue(prevValues => {
let newBoard = [...prevValues];
console.log("before: " + newBoard[rowIndex][cellIndex]);
newBoard[rowIndex][cellIndex] = newBoard[rowIndex][cellIndex] + 1;
console.log("after: " + newBoard[rowIndex][cellIndex]);
return newBoard;
}
);
}
The log:
the 3,0 square was clicked
before: 0
after: 1
the 3,0 square was clicked
before: 1
after: 2
before: 2
after: 3
As can be seen, from the second click the value is raised twice by every click.

You were still mutating state, if you have pure components then they won't re render when mutating. Doing the full state copy with JSON.parse is a bad idea if you have pure components because everything will be re rendered.
let newBoard = [...prevValues];
newBoard[rowIndex] = [...newBoard[rowIndex]];
newBoard[rowIndex][cellIndex] =
newBoard[rowIndex][cellIndex] + 1;

As mentioned by Udaya Prakash in the comment above, it's being called twice to make sure your setState is independent and idempotent. So, if I understand correctly, it being called twice is not a bug, but your values being changed the second time is.
Here's Dan Abramov's comment from the same GitHub issue:
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
We can fix it by deep-copying your prevValues instead of shallow-copying with spread operator. As you might already know, there are multiple ways to deep copy your object, we could go with JSON.parse(JSON.stringify(...) for now, which you can replace with your favorite kind from here
setSquaresValue(prevValues => {
let newBoard = JSON.parse(JSON.stringify(prevValues)); // <<< this
console.log("before: " + newBoard[rowIndex][cellIndex]);
newBoard[rowIndex][cellIndex] = newBoard[rowIndex][cellIndex] + 1;
console.log("after: " + newBoard[rowIndex][cellIndex]);
return newBoard;
});
I've kind of simulated it in codesandbox here if you want to play around.

Related

How to use setInterval and setTimeout with React hooks

I want to loop through an array of strings
When a new string, from the array, is selected I want to print out a
substring of the selected string every 0.1 second
After the entire string is printed I want to pause and then select the
next string in the array
Repeat
eg ['one', 'two']
output:
o
on
one
// pause 1 second
t
tw
two
// pause 1 second
o
on
one
// pause 1 second
I have tried this but it only loops through once
useEffect(() => {
let i = 0
function increment() {
i++
console.log(i)
}
const incrementTimer = setInterval(increment, 100)
setInterval(() => {
clearInterval(incrementTimer)
}, 1000)
}, [])
I created a codesandbox link: https://codesandbox.io/s/recursing-bash-hkmqrc?file=/src/App.js
This is done by rxjs as you are dealing with time related actions. rxjs is the best tool. After you reload the page, it will start to log values as you expect in 1s.
Can you please let me know if the result is what you want? Thanks.

setState in while loop causing "Too many re-renders. React limits the number of renders to prevent an infinite loop"

I am trying to calculate countDiff and add the object to the record array countDiff times. For example, if countDiff value is two then I would like to add two same objects with incremented tempRecordId.
However "Too many re-renders. React limits the number of renders to prevent an infinite loop" error came up. Do you know any way to setState in a loop or the way to figure out this logic?
const countDiff = homeRecordCount - awayRecordCount;
let i = 0;
let insertTeamId = countDiff > 0 ? awayTeamId : homeTeamId;
if (countDiff != 0)
{
while(countDiff != i)
{
setTempRecordId(tempRecordId + 1);
addRowToRecord({scoreMemberName:null, scoreMemberId: null, scoreTeamId: insertTeamId, assistMemberName: null, assistMemberId: null, matchId: matchId, codeId: 7, id: 0, tempRecordId: tempRecordId })
i++;
}
}
You shouldn't use conditionals nor loops in your Hook component. https://reactjs.org/docs/hooks-rules.html
In this case you could just add all the records at once.
The best way to handle this situation is to construct the final object you need before you ever set state in the first place ie move your set state to after the while loop.
It means once you call to setState then it will render your page. your logic set to never ending loop . I guess All the time (countDiff != i = > true)comes to inside of the while loop. Please set your logic with some ending condition.

Why is my Observable only sending a message to the most recent subscriber

I have 2 Angular (1.6.4) components and a service. The service creates 2 observables like this (coffeescript)...
// In service constructor
#observable = Rx.Observable.create (source) =>
source.next(123)
#observable2 = Rx.Observable.create (source) =>
#observer = source
$timeout =>
#observer.next(345)
, 1000
In my controllers I have these lines
//Component 1
service.observable.subscribe (v) => console.log('consumer A1: ' + v)
service.observable2.subscribe (v) => console.log('consumer A2: ' + v)
//Component 2
service.observable.subscribe (v) => console.log('consumer B1: ' + v)
service.observable2.subscribe (v) => console.log('consumer B2: ' + v)
I would expect to see 4 console logs but instead I see 3 (A1, B1, B2). Why is it then when I call from outside only the last subscribe gets called but when it is inside both get called? Is there a way to have the second example call both (preferably as an Observable and not a Subject)?
If I do use a Subject everything works as expected but I am still curious why it doesn't work as I would expect with an observable.
Your observable2 is taking the subscriber and storing it in a variable (#observer) each time something subscribes. So the 2nd subscription (B2) is replacing the value in the variable.
It is hard to say what the correct way to rewrite the 2nd observer should be because it isn't clear what your goal is. A Subject can certainly work. Observable.timer might also work, depending on your goal.
The Observable.timer way would be:
#observable2 = Observable.timer(1000);
Anything that subscribes to that would get an event 1 second after it subscribed.

how to code the utility functions in React without cluttering the component it'self

I have build this small react app with 3 components showing weatherforcaset from an api.
I am not sure how I have handled the utils functions in a separate node module is the correct way as it's pretty adhoc, mainly to do with the date handling and creating another 5 arrays with the data received from the api.
https://github.com/c-science1/weatherForecastReact/
Please can someone advise me if there is a better way if doing the same?
Many Thanks!
It looks like you can minimize the code in the function while keeping the DRY principle. You should refactor the following method:
this.createNewLists = function (dayName, itemP){
let itemDate = new Date(itemP.dt_txt);
let currentDate = new Date();
let nextDate = new Date(); ;
if (itemDate.getDate() == currentDate.getDate() ){
this.day1.push(itemP);
this.day1Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day2.push(itemP);
this.day2Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day3.push(itemP);
this.day3Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day4.push(itemP);
this.day4Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day5.push(itemP);
this.day5Name = dayName;
}
}
The pattern in this function are repeating them and you can better organize this code.
You can iterate from 1 to 5 (for the weekdays) and minimize your code and keep clean code and the DRY principle.
I would say for the size of your app it is just fine. Regarding things I might do differently, I would not use utils function wrapper, since you already have module serving as namespace.
As for weatherImage function I would probably put it into component file that uses it. If more than one component uses it, I would probably put it into components/common.js. createNewLists would probably also go to this module.

Protractor returning a value from a promise

I'm writing a test to see if my code is removing a level by looking at a text value on the screen which holds the count of levels.
it 'allows deleting level versions', ->
browser.get('/api#/costings')
element(By.id("edit")).click()
startCount = element(By.id("versions_count")).getText().then( (count) ->
return count
)
element(By.id("versions")).click()
first=element.all(By.id("listing")).first()
first.element(By.id("delete")).click()
helper.accept_dialog()
element(By.id("back")).click()
expect(element(By.id("versions_count")).getText()).toEqual(startCount - 1)
Problem here is startCount results in a function. I cannot seem to get startCount into an integer so that I can see if the count has gone down by 1 item.
It gives me the error;
1) edit an existing costing allows deleting level versions
Message:
Expected '1' to equal NaN.
If I try parseInt(startCount) I get the same error.
The variable startCount is a promise, and so startCount - 1 doesn't make sense: there is no automatic type conversion from a promise to its resolved value, so you can't subtract one from it.
What you can do, is create a promise whose resolved value is the expected versions count:
expectedCount = element(By.id("versions_count")).getText().then( (count) ->
return (count - 1).toString();
)
and then you can pass this promise to toEqual, as it automatically unwraps promises at the appropriate point in the control flow
expect(element(By.id("versions_count")).getText()).toEqual(expectedCount)
it("Verify Add Element", function(){
basePage.gotoAssetsPage();
var numberOfElementsBefore = assetsPage.getTotalAssertsNumber();//get text from counter
assetsPage.createAsset(); // add one object on the page
var numberOfElementsAfter = assetsPage.getTotalAssertsNumber(); // get text from counter after creation of object
numberOfElementsBefore.then(function(startNumberText) {
console.log("Number of Asserts before test is " + startNumberText);
return startNumberText;
});
numberOfElementsAfter.then(function(endNumberText) {
console.log("Number of Asserts after test is " + endNumberText);
expect(assertsBefore).toBe((endNumberText-1).toString());
});
});
In this test I'm verifying, that quantity of elements before text will equals quantity of elements after test minus 1.
If I not use .toString(), test result will be: Expect '21' to be 21. So, converting to string works. Maybe someone have a better solution :)

Resources