2

I need to play CSS animation on every time stock property of items state re-renders(increase button clicks)

React code:

const RenderingApp = () => {
  const [items, setItems] = useState([
    { fruit: 'banana', stock: 10, id: 0 },
    { fruit: 'apple', stock: 5, id: 1 },
  ]);

  const renderList = () => {
    return items.map((item, index) => {
      const handleIncrease = () => {
        const newItems = items.map(i => {
          if (i.id === item.id) {
            return { ...i, stock: i.stock + 1 };
          } else {
            return i;
          }
        });
        setItems(newItems);
      };

      return (
        <div key={index}>
          <div>{item.fruit}</div>
          <div className="add-color">{item.stock}</div>
          <button onClick={handleIncrease}>increase</button>
          <br />
          <br />
        </div>
      );
    });
  };

  return <div>{renderList()}</div>;
};

CSS code:

.add-color {
  animation-name: example;
  animation-duration: 0.8s;
}

@keyframes example {
  0% {
    background-color: #fff;
  }
  50% {
    background-color: rgb(5, 149, 5);
  }
  100% {
    background-color: #fff;
  }
}

However, animation plays once at initial render. To solve this problem, I added key value on the stock's div as it was answered in this question, like so:

 <div key={Math.random()} className="add-color">{item.stock}</div>

but now as I click increase button, for example of second item's button, both stock's div re-renders, therefore animation plays for both of them simultaneously:

enter image description here next click: enter image description here

before I added key value re-render only happened just clicked item's stock div but not played animation, though after adding key value, re-render happens for both item's stock div.

How can I achieve to play animation for just clicked item's stock div? Thanks in advance

Leonardo
  • 133
  • 2
  • 8
  • Does this answer your question? [How to trigger a CSS animation on EVERY TIME a react component re-renders](https://stackoverflow.com/questions/63186710/how-to-trigger-a-css-animation-on-every-time-a-react-component-re-renders) – Danial Aug 11 '21 at 11:49
  • It doesn't. As I mentioned, I have already added key but it triggers all items, I need to play CSS animation for specific item – Leonardo Aug 11 '21 at 12:19

1 Answers1

3

What you essentially want to do is restart a CSS animation for a particular element. To be honest, I'm not sure of a better way to do this in React than by accessing the DOM element with a reference.

You can do this by making each item its own component and using useRef to get access to the DOM element you want to animate, and then apply the animation code in the linked stack overflow answer above.

const RenderItem = ({ item, handleIncrease }) => {
    const stockElem = useRef()

    const animate = () => {
        let s = stockElem.current
        s.style.animation = 'none'
        // I had to do "let x =" here because my create-react-app config was yelling at me
        let x = s.offsetHeight; // trigger reflow
        s.style.animation = null
    }

    const increase = () => {
        handleIncrease()
        animate()
    }

    return (
        <div>
            <div>{item.fruit}</div>
            <div ref={stockElem} className="add-color">{item.stock}</div>
            <button onClick={increase}>increase</button>
            <br />
            <br />
        </div>
    )
}

And then in your main component, render each item:

return <RenderItem key={item.id} item={item} handleIncrease={handleIncrease} />;

The key approach

There is a way to do this with key, but I don't like it since it goes against key's purpose as defined by React, and can probably lead to problems down the road.

return (
  <div key={`${item.id}-${item.stock}`}>
    <div>{item.fruit}</div>
    <div className="add-color">{item.stock}</div>
    <button onClick={handleIncrease}>increase</button>
    <br />
    <br />
  </div>
);

The key line is <div key={`${item.id}-${item.stock}`}>. React will un-mount and remount an element whose key changes. So, this utilizes the uniqueness of item.id combined with the fact that item.stock changes to rerender the div.

Again, even though the first solution I provided is more verbose, it's definitely more canonical.

Auroratide
  • 2,299
  • 10
  • 15