"variable" is read only - javascript

I have the following code and I am getting error of "count" is read-only:
import React, {useState} from 'react';
import {View, Text, StyleSheet, Button} from 'react-native';
const CounterScreen = () => {
const [count, setCount] = useState(0);
return (
<View>
<Button onPress={() => setCount(count++)} title="Increase" />
<Button onPress={() => setCount(count--)} title="Decrease" />
<Text>Current Counter: {count}</Text>
</View>
);
};
const styles = StyleSheet.create({});
export default CounterScreen;
But if I use setCount(count + 1) instead of setCount(count++), it works perfectly. What is the difference here between count + 1 and count++

The important thing to remember is that you can change the value of a variable declared as a const, but you cannot reassign it. Here is a good blog post about this:
https://mathiasbynens.be/notes/es6-const
setCount(count+1) is taking the current value of the variable count, adding 1 to it and then passing this to our setter function, 'setCount' to update our state. We are in no way reassigning the 'count' variable. React is updating the value of 'count' under the hood using the setter function.
What you are doing with count++ is attempting to re-assign the count variable, not just change the value of the 'count' variable.

count++ is shorthand for count = count + 1; you are actually changing the value of a variable. not just reading its value.

count++ is similar as count = count + 1
means it's changing the variable itself
and you know const are read-only after initialization that is why you doing setCount() to update the value.
and count + 1 is not updating itself.

const [count, setCount] = useState(0);
Your count in here ↑ is read only, if you want to change it's value have to call setCount()
Like setCount(num)
so if num= 10 then equal to setCount(10)
So that setCount(count + 1) could work, count+1 is a number
But why setCount(count++) couldn't work is because
count++
Equal to
count=count+1; //It is changing the value of count directly!
setCount(count++)
setCount(count=count+1) //So count++ is changing the value of count directly, not through setCount, couldn't work.
It couldn't work and may error for "count" is read-only
This is try to change count value without calling setCount in setCount

Related

bad value includes() react

i'm trying to make a function that add a keyword if is not already in the array, if it is shows a error message, but the problem is that the includes() method doesn't work properly, it shows false first (when a word is not in the array), then true (because it is in the array) and then false again if i don't change the word.
I don't know why this is happening but it happens also with indexOf(). Maybe its a react problem with rendering or something like that.
Between if its another way to take an input value and do this, it is welcome
const [repeatedKeyWord, setRepeatedKeyWord] = useState(false)
let keyWords = []
const addKeyword = () => {
let keyword = document.getElementById('keyword').value;
const exist = keyWords.includes(keyword);
console.log(exist)
if (keyword && !exist){
console.log('in')
keyWords.push(keyword)
setRepeatedKeyWord(false)
}
setRepeatedKeyWord(exist? true : false)
console.log(keyWords)
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword} />
You must store your keywords in useState, otherwise you lose the value between re-renders of your component. Thereafter, you can use .includes on your array. React ensures that you'll always have the latest value (e.g. 'snapshot' of your state).
Also note that when you are trying to compute a new state value (i.e. updating your array) you are dependent on the previous value of your state. If that is the case, you can pass a function to the setState function. Have a look at this issue where I have included a link to a working codesandbox for updating previous state values.
As a side note, I would suggest to avoid using let to declare your variables. Only use the let keyword if you are certain that you will re-assign a new value to your variable. Otherwise using const might be better to avoid mistakes.
const [keywords, setKeyWords] = useState([])
const addKeyword = () => {
const keyword = document.getElementById('keyword').value;
return setKeywords((prevState) => {
if (prevState.includes(keyword)) {
return [...prevState]
}
return [...prevState, keyword]
})
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword}

How to run a series of Count Down Clock

I am trying to run multiple count-down from an array, e.g., [11, 12, 13, 14, 15, 16]. What I would like to achieve is that the first time set count down time to 11, when it reach 0, the timer is set to 12 and then count down to 0. After that, reset to 13 and count down, then reset to 14 and count down, etc.
However, my code can only count down from 11 to 0, and then stop. For loop seems not working and never put the second item 12 into the timer. I later found out it is because the retun inside for loop break it outside of the loop. I wonder if there are some smart ways to avoid return in a For-Loop? Or, how to use Return in For-Loop without breaking out of the loop?
(I have another counter there called TotalTime which I inteneded to count all the total time it takes, ie, 11+12+13+14,etc)
The Timer and Display Screen:
import {View, Text, StyleSheet, FlatList} from 'react-native';
import PlaySounds from './PlaySounds';
const CustomTimer = ({time, length}) => {
const [seconds, setSeconds] = useState(time);
const [totalTime, setTotalTime] = useState(0);
useEffect(() => {
if (seconds > 0 )
{ const interval = setInterval(() => {
setSeconds(seconds => seconds - 1);
setTotalTime(seconds=>seconds + 1)
}, 1000);
return () => clearInterval(interval);
}}, [seconds])
return (
<View>
<Text style={{fontSize:20}}> Count Down: {seconds} sec</Text>
<Text style={{fontSize:20}}> Total Time: {totalTime} sec</Text>
</View>
)}
export default CustomTimer;
=====================
import React, {useEffect, useState} from 'react';
import {SafeAreaView,View,Button,ScrollView, Text, StyleSheet} from 'react-native';
import CustomTimer from '../component/CustomTimer';
const BrewScreen = () => {
const timeArray= [11, 12, 13, 14, 15, 16]
const length = timeArray.length
const countDownArray=() =>{
for (let i=0; i<length; i++) {
return(<CustomTimer time={timeArray[i]} length={length}/>)
}
}
return (
<>
<ScrollView>
{countDownArray()}
</ScrollView>
</>
)
}
The issue seems to be that you return inside the for loop.
This means that during the first iteration of the for loop in the countDownArray function you return the Timer element. When you do this, the function will exit and so the loop will not continue.
What you would need instead to achieve your desired behaviour is a different way of creating the Timer elements. This will likely require a callback function in the Timer element. This can be used to update the state of the BrewScreen, and update the Timer displayed.
I finally solve it, not using loops but the all mighty useEffect:
const CustomTimer = ({time}) => {
const [seconds, setSeconds] = useState(time[0]);
const [count, setCount] = useState(1)
const [totalTime, setTotalTime] = useState(0);
useEffect(() => {
if (seconds >= 0 )
{ const interval = setInterval(() => {
setSeconds(seconds => seconds - 1);
setTotalTime(seconds=> seconds + 1)
console.log('seconds ', seconds, ' totalTime ', totalTime)
}, 1000);
return () => clearInterval(interval);}
else if (count<(time.length)) {
setCount(count => count+1)
setSeconds(time[count])}
else return
}
)
return (
<View>
<Text style={{fontSize:20}}> Count Down: {seconds} sec</Text>
<Text style={{fontSize:20}}> Total Time: {totalTime} sec</Text>
{seconds===3?<PlaySounds/>:null}
</View>
)}
Having Return inside For Loop will break out of the loop
JS will execute all the i in one go so you need to add Promise with Async/Await pair
Return won't break ForEach loop
However, Async does not work in ForEach loop
useEffect automatically run the loop with proper support of time Interval
In my case where I want to run a count-down clock, setInterval is must preferred to setCountDown, since setInterval renew EVERY milisecond you specified.
Remeber to clean up your interval with clearInterval
There is one thing I don't understand: The initial value of count has to be 1 instead of 0. It seems like count is forced into initial value the first time it is read. If I initialized it as 0, the first number in array will be executed twice.
Also, the final number displayed in Count Down is -1.... I know it is because this is how the useEffect is stopped, but wonder how to avoid showing a negative number?

change state with conditionals inside a UseEffect (simple counter with hooks)

I'm trying to build a simple percentage counter from 0 to 100 that is updating itself using SetInterval() inside useEffect(). I can get the counter work but I would like to restart the counter once it reaches the 100%. This is my code:
const [percentage, setPercentage]=useState(0);
useEffect(() => {
const intervalId= setInterval(() => {
let per = percentage=> percentage+1
if(per>=100){
per=0
}
setPercentage(per)
}, 100);
return () => {
}
}, [])
Inside the console I can see the state is increasing but it will ignore the if statement to reset the state to 0 once it reaches 100. How can I tackle this knowing that if conditionals are not great with hooks setState?
Check the percentage instead of per. per is of type function and will never be greater or equal to 100, percentage is the value that will reach 100.
This will make your effect depend on percentage which you have avoided by using the function. In this situation, if I still don't want to add that dependency, then I might use a reducer instead to manage that state. This way I don't need to depend on percentage inside of the useEffect.
const reducer = (state, action) => state >= 100 ? 0 : state + 1;
The way you would do this while keeping useState is by moving the check into the state setting function.
setPercentage(percentage => percentage >= 100 ? 0 : percentage + 1);
This might be the quicker option for you. Notice how similar these are, in the end useState is implemented using the useReducer code path as far as I know.
Below should work.
const [percentage, setPercentage] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setPercentage(prev => prev >= 100 ? 0 : prev + 1);
}, 100);
return () => clearInterval(intervalId);
}, [])
Note that per in your code is a function and therefore cannot used to compare against numbers. You may also want to clear the interval in destruction of component.

why is useEffect rendering unexpected values?

I am trying to create a scoreboard for a quiz application. After answering a question the index is updated. Here is the code for the component.
export const ScoreBoard = ({ result, index }) => {
const [score, setScore] = useState(0)
const [total, setTotal] = useState(0)
const [rightAns, setRight] = useState(0)
useEffect(() => {
if(result === true ) {
setRight(rightAns + 1)
setTotal(total + 1)
}
if(result === false) {
setTotal(total + 1)
}
setScore(right/total)
}, [index]);
return (
<>
<div>{score}</div>
<div>{rightAns}</div>
<div>{total}</div>
</>
)
}
When it first renders the values are
score = NaN
rightAns = 0
total = 0
After clicking on one of the corrects answers the values update to
score = NaN
rightAns = 1
total = 1
and then finally after one more answer (with a false value) it updates to
score = 1
rightAns = 1
total = 2
Score is no longer NaN but it is still displaying an incorrect value. After those three renders the application begins updating the score to a lagging value.
score = 0.5
rightAns = 2
total = 3
What is going on during the first 3 renders and how do I fix it?
You shouldn't be storing the score in state at all, because it can be calculated based on other states.
All the state change calls are asynchronous and the values of state don't change until a rerender occurs, which means you are still accessing the old values.
export const ScoreBoard = ({ result, index }) => {
const [total, setTotal] = useState(0)
const [rightAns, setRight] = useState(0)
useEffect(() => {
if(result === true ) {
setRight(rightAns + 1)
setTotal(total + 1)
}
if(result === false) {
setTotal(total + 1)
}
}, [index]);
const score = right/total
return (
<>
<div>{score}</div>
<div>{rightAns}</div>
<div>{total}</div>
</>
)
}
Simpler and following the React guidelines about the single "source of truth".
Your problem is that calling setState doesn't change the state immediately - it waits for code to finish and renders the component again with the new state. You rely on total changing when calculating score, so it doesn't work.
There are multiple approaches to solve this problem - in my opinion score shouldn't be state, but a value computed from total and rightAns when you need it.
All of your set... functions are asynchronous and do not update the value immediately. So when you first render, you call setScore(right/total) with right=0 and total=0, so you get NaN as a result for score. All your other problems are related to the same problem of setScore using the wrong values.
One way to solve this problem is to remove score from state and add it to the return like this:
return (
<>
{total > 0 && <div>{right/total}</div>}
<div>{rightAns}</div>
<div>{total}</div>
</>
)
You also can simplify your useEffect:
useEffect(() => {
setTotal(total + 1);
if(result === true ) setRight(rightAns + 1);
}, [index]);
With how you have it set up currently, you'd need to make sure that you are updating result before index. Because it seems like the useEffect is creating a closure around a previous result and will mess up from that. Here's showing that it does work, you just need to make sure that result and index are updated at the right times.
If you don't want to calculate the score every render (i.e. it's an expensive calculation) you can useMemo or useEffect as I have shown in the stackblitz.
https://stackblitz.com/edit/react-fughgt
Although there are many other ways to improve how you work with hooks. One is to make sure to pay attention to the eslint react-hooks/exhaustive-deps rule as it will forcefully show you all the little bugs that can end up happening due to how closures work.
In this instance, you can easily calculate score based on total and rightAns. And total is essentially just index + 1.
I'd also modify the use effect as it is right now to use setState as a callback to get rid of a lot of dependency issues in it:
useEffect(() => {
if (result === true) {
setRight(rightAns => rightAns + 1);
setTotal(total => total + 1);
}
if (result === false) {
setTotal(total => total + 1);
}
}, [index]);
useEffect(()=>{
setScore(rightAns / total ||0);
},[rightAns,total])

Why is my value from state hook different than what I'm logging in my useEffect?

If useEffect runs after the render phase, why is my value in useEffect less than what is being shown in the return?
I have a component that will update value whenever my counter changes on cleanup
const [value, setValue] = useState(0);
const [counter, setCounter] = useState(0);
useEffect(() => {
console.log(`value = ${value} from effect`);
return () => {
setValue(v => v + 1);
console.log(`value = ${value} from cleanup`);
};
}, [counter]);
return (
<div>
<button onClick={() => setCounter(v => v + 1)}>Increment Counter</button>
<p>value: {value}</p>
</div>
);
On first increment, the value in my return will be 1, but my useEffect will log it as 0. Why would those values be different and why would the useEffect not log 1 as well? This component doesn't have a real purpose, just something I'm experimenting with
Let's step through your component and look at what's actually happening. I will mark value and counter with their respective render to show which version is used like value_1 for first rerender.
Mount
value_0 = 0, counter_0 = 0.
Effect is run. log value_0. register cleanup using value_0.
return JSX using value_0.
Event: user clicks increment. counter is set to 1.
1. rerender triggered by setCounter
value_1 = 0, counter_1 = 1.
Compare deps, [0] elements differ from [1]. Cleanup is run. log value_0. set value to 1. Effect is run. log value_1. register cleanup using value_1.
return JSX using value_1.
2. rerender triggered by setValue
value_2 = 1, counter_2 = 1.
Compare deps, [1] elements match [1].
return JSX using value_2.
-
You will notice that value and counter are constants for each render. They are defined as const after all. Whenever cleanup is run, the function that is executed is the one returned by the last render, which used its own version of value.
The effect is never run on the last update where value is finally up to date because the deps haven't changed.
You can avoid this kind of confusion by using the react-hooks/exhaustive-deps lint rule. It will stop you accidentally using out-of-date values in your effects, callbacks or memoised values.

Categories