Deva44
Deva44

Reputation: 93

React add and remove a component on button click

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.

  1. What might be going wrong here?
  2. Also any better approach to do the same is welcome.
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

Answers (4)

Jisatsu
Jisatsu

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

Elisey
Elisey

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

Dennis Vash
Dennis Vash

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

cbdeveloper
cbdeveloper

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

Related Questions