Reputation: 93
I'm new to React. Here I'm trying to add/remove a 'Timer' component on click of +/- button. It works fine on click of '+', but not on '-'. I was of the opinion that every time I set a state, the component re-renders. But here it doesn't render on click of '-' button.
function App() {
var [timers, setTimers] = useState([]);
var [count, setCount] = useState(0);
const addTimer = () => {
setCount(count + 1);
timers.push(count);
setTimers(timers);
console.log(timers); //[0]
};
const removeTimer = () => {
timers.pop();
setTimers(timers);
console.log(timers); //[]
};
return (
<div>
<button className="button" onClick={addTimer}>
+
</button>
<button className="button" onClick={removeTimer}>
-
</button>
<div>
{timers.map((t) => (
<Timer key={t} />
))}
</div>
</div>
);
}
Upvotes: 2
Views: 6645
Reputation: 56
May be the reason why it doesn't work is that you add/remove timers in wrong way using push/pop methods.
In case you want your components to rerender, you need to change state by using distructuring.
May be not the best solution but should works:
const addTimer = () => {
setCount(count + 1)
setTimers([...timers, count])
}
const removeTimer = () => {
let filteredTimers = timers.filter((timer, index) => index !== (timers.length-1))
setTimers([...filteredTimers])
}
Upvotes: 1
Reputation: 147
Because you're trying to mutate state. There is principle here (in React) to not mutating the state. So you should not pop but do something like that in remove function. It helps you avoiding any side effects:
setTimers(state => state.filter((timer, index) => index !== state.length - 1))
tested with React sandbox
Upvotes: 1
Reputation: 53894
You are mutating the state which not triggers a render, see Power Of Not Mutating Data in React docs.
Rewriting to immutable logic can be:
const addTimer = () => {
setCount((prevCount ) => prevCount + 1);
setTimers((prevTimers) => [...prevTimers, count]);
};
const removeTimer = () => {
setTimers((prevTimers) => prevTimers.slice(0, prevTimers.length - 1));
};
Upvotes: 1
Reputation: 31425
The problem is that react only re-renders if the state changes. And it checks that fact by doing a shallow comparison. So, for objects (or arrays, like your case), even though you are changing the content of the object, the object reference is remaining the same. Because timers.pop()
does not create a new array. It modifies the current array.
And React will do something like that: old array === new array ?
and that will come back as true
, because it's the same array/object reference.
PS: Your (+) button is only working because your also doing setCount(count+1)
. By the way, you should change that to setCount((prevState) => prevState + 1);
When your new state depends on your last state, I advise you to always use this form of the setState
call:
setState((prevState) => {
// READ AND USE OLD prevState
// RETURN BRAND NEW newState
});
You could do something like:
const addTimer = () => {
setCount((prevState) => prevState + 1);
setTimers((prevState) => {
const newTimers = Array.from(prevState); // CREATING A NEW ARRAY OBJECT
timers.push(count);
return newTimers;
});
};
const removeTimer = () => {
setTimers((prevState) => {
const newTimers = Array.from(prevState); // CREATING A NEW ARRAY OBJECT
newTimers.pop();
return newTimers;
});
};
Upvotes: 4