Add/remove state array item immutably - javascript

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
};

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);

React child not updating a variable of parent through function

So I'm learning react at the moment and I've been struggling with this issue..
I'm trying to do tic-tac-toe so I've got this code:
import './App.css';
import { useState } from "react"
const X = 1;
const O = -1;
const EMPTY = 0;
var Square = ({idx, click}) =>
{
let [val, setVal] = useState(EMPTY);
return (
<button onClick={() => {click(setVal, idx);}}>{val}</button>
)
}
var Logger = ({state}) =>
{
return (
<button onClick={() => {console.log(state);}}>log</button>
)
}
var App = () =>
{
let [turn, setTurn] = useState(X);
let state = new Array(9).fill(EMPTY);
let squares = new Array(9);
let click = (setValFunc, idx) =>
{
setTurn(-turn);
setValFunc(turn);
state[idx] = turn;
}
for (let i = 0 ; i < 9 ; i++)
{
squares[i] = (<Square click={click} idx={i}/>);
}
return (
<>
<Logger state={state} />
<div>
{squares}
</div>
</>
)
}
export default App;
so the squares ARE changing as I click them, but when I click the log button to log the state array to the console, the state array remains all zeros.
what am I missing here?
Your state has to be a React state again. Otherwise, the state you defined inside the App as a local variable only lasts until the next rerender.
Maintain tic-tac-toe state inside a useState hook
let [state, setState] = useState(new Array(9).fill(EMPTY));
Update the click handler accordingly.
let click = (setValFunc, idx) => {
setTurn(-turn);
setValFunc(turn);
setState((prevState) =>
prevState.map((item, index) => (index === idx ? turn : item))
);
};
In React, the state concept is important.
In your case, you need to understand what is your state and how you can model it.
If you are doing a Tic-Tac-Toe you will have:
the board game: a 3 by 3 "table" with empty, cross or circle signs
This can be modeled by an array of 9 elements as you did.
But then you need to store this array using useState otherwise between -re-renders your state array will be recreated every time.
I advise you to read https://reactjs.org/docs/lifting-state-up.html and https://beta.reactjs.org/learn.

React dynamically add html element with event handler

Is it possible to append buttons with event handler in react ?
// This is working fine but its pure javascript onclick , how can I make this element with react js onClick ?
This is reference code , my scenario is appending html code dynamically
const Button = () => {
const dynamicElement = () => {
let li = document.createElement('li');
li.className = 'dynamic-link'; // Class name
li.innerHTML = "I am New"; // Text inside
document.getElementById('links').appendChild(li);
li.onclick = function(){ alert(0) }
}
useEffect(() => {
dynamicElement();
}, [])
}
You can just convert your function to a functional component like this.
const DynamicElement = () => {
return (
<li onClick={()=>alert(0)} className="dynamic-link">I am New</li>
)
}
const Button = () => {
const [visible, setVisible] = useState(false);
useEffect(()=>setVisible(true), [])
// if visible is true return <DynamicElement/>, either way return null
return visible ? <DynamicElement/> : null
}
BTW useState is hooks implementation of a state

React useState not working with conditional operator

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>
)

Can't reset filter after clearing input ( react)

Have filter, it's filtering ok, but when clear input. I see filtered result:
filterList(event) {
var updatedList = this.state.items;
if (event.target.value !== '') {
updatedList = updatedList.filter(function(item){
return item.name.toLowerCase().indexOf(event.target.value.toLowerCase()) !== -1;
});
}
this.setState({items: updatedList}); // now this.state.items has a value
}
My condition not working.
https://plnkr.co/edit/dPZM9BZVa4uzEMwdNpZ8?p=catalogue full component in script.js
There's a better approach for this. You usually don't want to use props to set your state directly. What you want is to only use the state for filtered item sets, since your props will always contain the full set of items. So first, remove your componentWillReceiveProps function. Then change the following things:
// in your constructor
this.state = {
filteredItems: false
}
filterList(e) {
var value = e.target.value;
this.setState({
filteredItems: !value
? false
: this.props.items.filter(function(item) {
return item.name.toLowerCase().indexOf(value.toLowerCase()) > -1
})
})
}
// inside render
var elems = this.state.filteredItems || this.props.items

Categories

Resources