reactjsreact-transition-group

How to animate an array being updated in react?


I'm trying to animate an array that's being updated by signals but cannot seemingly get the transitions to fire properly. Maybe I'm approaching it the wrong way, haven't worked much with animations.

What I'm trying to achieve is that when the array is updated with setItems it should run the exiting/exited for the items array and entering / entered for the updated actualData array.

My thinking is that when the array gets updated the old elements gets removed from the DOM and then trigger the exiting transitioning and vice versa for items that gets added to the DOM trigger the entering transition. Maybe here's where Im going wrong?

If anyone can point me in the right direction I would be really grateful!

I have a working example of what I've got so far here

And here is the code if the link isnt working:

import React, { useState, useEffect } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';

import './style.css';

export function App() {
  const [items, setItems] = useState([
    'Loading...',
    'Loading...',
    'Loading...',
  ]);
  const [actualData, setActualData] = useState(null);

  const defaultStyle = {
    transition: 'opacity 500ms, transform 500ms',
    opacity: 0,
    transform: 'translateY(-20px)',
  };

  const transitionStyles = {
    entering: { opacity: 1, transform: 'translateY(0)' },
    entered: { opacity: 1, transform: 'translateY(0)' },
    exiting: { opacity: 0, transform: 'translateY(-20px)' },
    exited: { opacity: 0, transform: 'translateY(-20px)' },
  };

  useEffect(() => {
    setTimeout(() => {
      setActualData(['Data 1', 'Data 2', 'Data 3']);
    }, 3000);
  }, []);

  useEffect(() => {
    if (actualData) {
      const timeout = setTimeout(() => {
        setItems(actualData);
      }, 500);

      return () => clearTimeout(timeout);
    }
  }, [actualData]);

  return (
    <TransitionGroup>
      {items.map((item, index) => (
        <CSSTransition key={index} timeout={500}>
          {state => (
            <div
              style={{
                ...defaultStyle,
                ...transitionStyles[state],
              }}
            >
              {item}
            </div>
          )}
        </CSSTransition>
      ))}
    </TransitionGroup>
  );
}

Disclaimer that I'm new to React and I've mixed different tutorials to get it to work so it may contain none best practices and such.


Solution

  • I'm not sure about your question. But, what I understand is that you want to show the transition to user, that old data are removed & new data are displayed.

    You have to update your items with an empty array & then you have to assign new values.

    React.useEffect(() => {
        if (actualData) {
            setItems([]);
            const timeout = setTimeout(() => {
                setItems(actualData);
            }, 500);
    
            return () => clearTimeout(timeout);
        }
    }, [actualData]);
    

    function App() {
      const [items, setItems] = React.useState([
        'Loading...',
        'Loading...',
        'Loading...',
      ]);
      
      const [actualData, setActualData] = React.useState(null);
    
      const defaultStyle = {
        transition: 'opacity 500ms, transform 500ms',
        opacity: 0,
        transform: 'translateY(-20px)',
      };
    
      const transitionStyles = {
        entering: { opacity: 1, transform: 'translateY(0)' },
        entered: { opacity: 1, transform: 'translateY(0)' },
        exiting: { opacity: 0, transform: 'translateY(-20px)' },
        exited: { opacity: 0, transform: 'translateY(-20px)' },
      };
      
      React.useEffect(() => {
        setTimeout(() => {
          setActualData(['Data 1', 'Data 2', 'Data 3']);
        }, 3000);
      }, []);
    
      React.useEffect(() => {
        if (actualData) {
          setItems([]);
          const timeout = setTimeout(() => {
            setItems(actualData);
          }, 500);
    
          return () => clearTimeout(timeout);
        }
      }, [actualData]);
    
      return (
        <ReactTransitionGroup.TransitionGroup>
          {items.map((item, index) => (
            <ReactTransitionGroup.CSSTransition key={index} timeout={500}>
              {state => (
                <div
                  style={{
                    ...defaultStyle,
                    ...transitionStyles[state],
                  }}
                >
                  {item}
                </div>
              )}
            </ReactTransitionGroup.CSSTransition>
          ))}
        </ReactTransitionGroup.TransitionGroup>
      );
    }
    
    ReactDOM.render(<App />, document.getElementById("root"));
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-transition-group/4.4.5/react-transition-group.min.js"></script>