React useState not working with conditional operator - javascript

This is my codes in React.
const [click, setClick] = useState(false);
const [like, setLike] = useState(dataObj.likedCount); // like === 4
const onClick = () => {
console.log(like);
console.log(click);
return setClick(!click) ? setLike(like - 1) : setLike(like + 1);
};
return (
<Like onClick={onClick}>{like}</Like>
)
And this is console page.
my trouble is that 'like number' is only increasing, not decreasing.
Even if boolean is changing, true to false.
I can't find anything wrong on my codes.... :((

You're using the return value of setClick. The setter function from useState doesn't have a documented return value. Experimentation suggests it always returns undefined. So you can't rely on the return value from setClick.
If your goal is to use the value of !click, do so directly:
setClick(!click);
if (!click) {
setLike(like - 1); // Or you may want + 1 here
} else {
setLike(like + 1); // Or you may want - 1 here
}
or
setClick(!click);
setLike(like + (click ? 1 : -1)); // Again, you may want to swap 1 and -1,
// depending on whether you want to use the
// old value or the new one

Some notice points:
Use state instead of setClick(!click) for conditional operator
do not return in onClick(), use props value (checked) to make it fully controlled
const [click, setClick] = useState(false);
const [like, setLike] = useState(dataObj.likedCount);
const onClick = () => {
setLike(like + (click ? -1 : 1)); // status before click been used here
setClick(!click);
};
return (
<Like onClick={onClick} value={click}>{like}</Like>
)

Related

How to set state with a setInterval() when a button is clicked?

I'm trying to update the count of activeIndex within the setInterval when the checkbox is ticked. handleNext() and handlePrevious() are working fine when buttons are clicked but when the checkbox is checked the value of activeIndex is not getting updated in handleNext() but it's getting updated on the screen, so there is no condition check for activeIndex and it goes beyond 3.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [slideTimer, setSlideTimer] = useState(null);
const [activeIndex, setActiveIndex] = useState(0);
const slideDuration = 1000;
const handleNext = () => {
if ((activeIndex) >= 3) {
setActiveIndex(0);
} else {
setActiveIndex(prev => prev + 1);
}
};
const handlePrev = () => {
if (activeIndex <= 0) {
setActiveIndex(3);
} else {
setActiveIndex((prev) => prev - 1);
}
};
const toggleSlider = () => {
if (slideTimer) {
clearInterval(slideTimer);
setSlideTimer(null);
return;
}
setSlideTimer(setInterval(handleNext, slideDuration));
};
return (
<div className="App">
<h1>{activeIndex}</h1>
<button onClick={() => handleNext()}>Next</button>
<button onClick={() => handlePrev()}>Previous</button>
<input onClick={() => toggleSlider()} type="checkbox" />
</div>
);
}
I have tried putting the code for toggleSlider inside useEffect() but that too is not working.
Your problem is that when handleNext gets defined (which occurs on every rerender), it only knows about the variables/state in its surrounding scope at the time that it's defined. As a result, when you queue an interval with:
setInterval(handleNext, slideDuration)
You will be executing the handleNext function that only knows about the component's state at that time. When your component eventually rerenders and sets a new value for activeIndex, your interval will still be exeucting the "old" handleNext function defined in the previous render that doesn't know about the newly updated state. One option to resolve this issue is to make the hanldeNext function not rely on state obtained from its outer scope, but instead, use the state setter function to get the current value:
const handleNext = () => {
setActiveIndex(currIndex => (currIndex + 1) % 4);
};
Above I've used % to cycle the index back to 0, but you very well can also use your if-statement, where you return 0 if currIndex >= 3, or return currIndex + 1 in your else.
I would also recommend that you remove the slideTimer. This state value isn't being used to describe your UI (you can see that as you're not using slideTimer within your returned JSX). In this case, you're better off using a ref to store your interval id:
const slideTimerRef = useRef(0); // instead of the `slideTimer` state
...
clearInterval(slideTimerRef.current);
slideTimerRef.current = null;
...
slideTimerRef.current = setInterval(handleNext, slideDuration);

How to increment a state variable every second in React

I have a list of images in my code for this component called "url". I also have a state variable called currImageIndex. What I want to happen is every time this component is rendered, I would like to start a timer. Every second this timer runs, I would like to increment my state variable currImageIndex. When currImageIndex + 1 is greater than or equal to url length, I would like to reset currImageIndex to zero. The overall goal is to simulate an automatic slideshow of images by just returning <img src={url[currImageIndex]}>. However, I am having issues with incrementing my state variable. I followed some code I found on here, but I can't figure out how to get the increment working. Here is my code.
const { url } = props;
const [currImageIndex, setCurrImageIndex] = useState(0);
console.log(url)
const increment = () => {
console.log(url.length)
console.log(currImageIndex)
if (currImageIndex + 1 < url.length) {
setCurrImageIndex((oldCount) => oldCount + 1)
}
else {
setCurrImageIndex(0)
}
}
useEffect(() => {
const id = setInterval(increment, 1000);
return () => clearInterval(id);
}, []);
return (
<div className={styles.container}>
<div className={styles.header}>Preview of GIF:</div>
<img src={url[currImageIndex]} alt="GIF Preview" className={styles.image} />
<div>{currImageIndex}</div>
</div>
);
When I run this with a url of size 2, the third to last line ("{currImageIndex}") displays the 1 then 2 then 3 and so on. It does not reset ever. My console.log(url.length) prints 2 and my console.log(currImageIndex) prints 0. What I want to happen is currImageIndex should be 0, 1, 0, 1, 0, 1,.... Can anyone help me understand what I am doing wrong? Thank you!
Issue
The issue here is you've a stale enclosure of the currImageIndex state value from the initial render when the increment callback was setup in the interval timer. You're correctly using a functional state to increment the currImageIndex value, but the currImageIndex value in the increment function body is and always will be that of the initial state value, 0.
Solution
A common solution here would be to use a React ref to cache the currImageIndex state so the current value can be accessed in callbacks.
Example:
const [currImageIndex, setCurrImageIndex] = useState(0);
const currentImageIndexRef = useRef();
useEffect(() => {
// cache the current state value
currentImageIndexRef.current = currentImageIndex;
}, [currImageIndex]);
const increment = () => {
// access the current state value
if (currentImageIndexRef.current + 1 < url.length) {
setCurrImageIndex((oldCount) => oldCount + 1)
}
else {
setCurrImageIndex(0)
}
}
useEffect(() => {
const id = setInterval(increment, 1000);
return () => clearInterval(id);
}, []);
A simpler solution is to just access the url value that is closed over in component scope with the state updater callback. Use a ternary operator to check if adding 1 to the current value is still less than the url length and state + 1, otherwise reset by returning 0. Note however that if the url array is dynamic and the length changes this solution breaks for the same reason, the url value from the initial render is closed over in scope.
const increment = () => {
setCurrImageIndex((count) => count + 1 < url.length ? count + 1 : 0);
}
And IMO the simplest solution is to just increment the currImageIndex state always and take the modulus of the url length to always compute a valid, in-range index value. If the url array changes, it doesn't matter as the modulus function will always compute a valid index.
const { url } = props;
const [index, setIndex] = useState(0);
const increment = () => setIndex(i => i + 1);
useEffect(() => {
const id = setInterval(increment, 1000);
return () => clearInterval(id);
}, []);
const currImageIndex = index % url.length;
return (
<div className={styles.container}>
<div className={styles.header}>Preview of GIF:</div>
<img src={url[currImageIndex]} alt="GIF Preview" className={styles.image} />
<div>{currImageIndex}</div>
</div>
);
The reason is that the callback passed on setInterval only access the currImageIndex variable in the first render, not the new value while render so you will always get currImageIndex = 0 and pass the condition if (currImageIndex + 1 < url.length). I update your code and make it work like your expectation: https://stackblitz.com/edit/react-y6fqwt

Add/remove state array item immutably

I've set up some buttons and can store in an array which one is clicked or not with a function (clickHandle). So I've set up a condition in className so that if the button is clicked or not it should change className. However this doesn't seem to work and I'm guessing because the className needs to be updated through a function.
Here's my code :
const [click, setClick] = useState([]);
const clickHandle = (id) => {
click.indexOf(id) === -1
? click.push(id)
: click.splice(click.indexOf(id), 1);
console.log(click);
};
return (
<div className="options-container">
{data &&
data.additionalCharges.map((data, index) => {
return (
<button
onClick={async () => clickHandle(data.id)}
className={
click.indexOf(data.id) === -1
? "options-unclicked"
: "options-clicked"
}
key={data.id}
>
<div>{data.title}</div>
<div>{data.description}</div>
<div>
{data.price.amount} € {data.price.unit}
</div>
</button>
);
})}
</div>
)```
The problem is that you're using Array.prototype.push() and Array.prototype.splice(), both mutating your state in-place, which doesn't let React detect the changes you've made.
Instead, your state manipulation approach should be immutable. So, your click handler should look something, like:
const clickHandle = (id) => {
click.includes(id)
? setClick(click.filter(_id => _id !== id))
: setClick([...click, id])
}
You have not update the state in your onClick listener clickHandle
Should be something like,
const clickHandle = (id) => {
let newData = [...click];
newData.indexOf(id) === -1
? newData.push(id)
: newData.splice(newData.indexOf(id), 1);
setClick(newData);
};
This way, your component will re-render on click and classname will be reflected.
This is because you are adding and removing buttons using click variable instead you should use setClick so that your component you get re-rendered
replace your clickHandler with following
const clickHandle = (id) => {
let buttons = [...click]
buttons.indexOf(id) === -1
? buttons.push(id)
: buttons.splice(buttons.indexOf(id), 1);
console.log(buttons);
setClick(buttons) // this will render your component
};

How to use ternary operator to set a variable to true or false based on value using javascript?

i want to set a variable to true or false based on a condition using ternary operator in javascript.
below is what i am trying to do
i am retrieving the value of "source" using formikBag.values and if that value is "source_1" i want to display span element if its anything other than "source_1" i dont want to display span element.
below is my code,
const source = formikBag.values[SOURCE];
const isSource1 = React.useMemo(() => ({
if (source === "source_1") {
return true;
} else {
return false;
}
}), [source]);
return (
{isSource1 &&
<span>source name</span>
}
);
in the above code i have used if and else but i want to use ternary operator to do the same. could someone help me with this.
You don't need to use if else clause just use your condition directly
const isSource1 = React.useMemo(() => source === "source_1"), [source]);
and also you don't need to use useMemo hook it doesn't do anything useful in your case but adds some extra unnecessary work, just use it directly
const isSource1 = source === "source_1"
const isSource1 = React.useMemo(() => ({
let retValue = (source === "source_1")? true : false
return retValue
}), [source]);
OR
const isSource1 = React.useMemo(() => ({
return (source === "source_1")? true : false
}), [source]);

React - Trying to reference this.props inside reduce()-function

I have little experience with JS and I'm following a video course on React. Most things makes sense but I'm having some trouble with reference scopes.
Here's what the tutor writes:
const orderIds = Object.keys(this.props.order);
const total = orderIds.reduce((prevTotal, key) => {
const fish = this.props.fishes[key];
const count = this.props.order[key];
const isAvailable = fish && fish.status === 'available';
if (isAvailable === true) {
return prevTotal + count * fish.price;
} else {
return prevTotal;
}
}, 0);
He is using an arrow-function. It works of course.
I want to do the same but with writing a "normal" function, because for now I don't want to make any shortcuts:
const orderIds = Object.keys(this.props.order);
const total = orderIds.reduce(
function(prevTotal, key) {
const fish = this.props.fishes[key];
const count = this.props.order[key];
const isAvailable = fish && fish.status === 'available';
if (isAvailable === true) {
return prevTotal + (count * fish.price);
} else {
return prevTotal;
}
}
, 0);
The problem is:
TypeError: this is undefined
So there's a problem with the scope of the reference to this.props...
I got this to work within a .map()-function by passing it a second argument of
... , this)
from searching online about referencing this.
That doesn't work for the reduce()-function. So, what can one do in my situation? Again, I don't want to use the shorter arrow-function because it's like a shortcut I don't want to use when learning. I've searched online about scopes on this and also about the reduce()-function but the examples I've seen doesn't quite fit what I'm looking for, or I'm simply not savvy enough to use what they're writing.
Thanks a ton if you can help me out, regards / HW
Arrow functions has parent scope so this represents parent's this but simple function don't. You can tell the function about this using .bind(this)
const total = orderIds.reduce(
(function(prevTotal, key) {
const fish = this.props.fishes[key];
const count = this.props.order[key];
const isAvailable = fish && fish.status === 'available';
if (isAvailable === true) {
return prevTotal + (count * fish.price);
} else {
return prevTotal;
}
}).bind(this)
, 0);
You can use bind as suggest upper or rewrite code like this:
const orderIds = Object.keys(this.props.order);
const { fishes, order } = this.props;
const total = orderIds.reduce(
function(total, key) {
const fish = fishes[key];
const count = order[key];
const isAvailable = fish && fish.status === 'available';
if (!isAvailable) return total;
return total + (count * fish.price);
}
, 0);

Categories

Resources