Search code examples
reactjsreact-spring

useTransition with react-spring as a component changes


I'm attempting to animate a card in and out. If there is a selected value, the card appears. If the selected item is undefined, the card disappears. I got this to work.

The next thing I tried to do is make it that if the selection changed (A new item) - animate out a card and animate in a new one. I'm confused on how to make this work... here is what I've attempted that kind of works.

Clearly I'm not understanding how this should be done. I'm wondering if I need to break this up into two cards and run useChain.

const App: React.FC = () => {

  //...

  const [selectedItem, setSelectedItem] = useState<TimelineItem | undefined>(undefined);
  const [lastSelectedItem, setLastSelectedItem] = useState<TimelineItem>({
    content: '',
    start: new Date(),
    id: 0,
  });

 //...

  const transitions = useTransition(
    [selectedItem, lastSelectedItem],
    item => (item ? item.id : 0),
    {
      from: { opacity: 0 },
      enter: { opacity: 1 },
      leave: { opacity: 0 },
    }
  );

  return (
      <Timeline
        onItemSelect={item => {
          if (selectedItem) setLastSelectedItem(selectedItem);
          setSelectedItem(item);
        }}
      />
      {transitions.map(({ item, key, props }) => {
        return (
          item && (
            <animated.div style={props}>
              {item === selectedItem ? (
                <ItemDetails
                  item={selectedItem} // If the selected item is undefined, this will not be running (happens when unselecting something)
                  groups={groups}
                  key={key || undefined} // key becomes undefined since item is
                ></ItemDetails>
              ) : (
                false && ( // The last item never shows, it still has the data for the lastSelectedItem (For the fade out while the new Item is being shown or there is no new item).
                  <ItemDetails
                    item={lastSelectedItem}
                    groups={groups}
                    key={key || undefined}
                  ></ItemDetails>
                )
              )}
            </animated.div>
          )
        );
      })}
  );
};

Solution

  • If I understand you well, you want to display the state of an array. New elements fade in and old one fades out. This is the functionality the Transition created for. I think it can be done a lot simpler. I would change the state managment and handle the array in the state. And the render should be a lot simpler.

    UPDATE: I created an example when the animation of the entering element wait for the animation of the leaving element to finish. I made it with interpolation. The o value changes from 0 to 1 for enter, and 1 to 2 for leave. So the opacity will change:

    leave: 1 -> 0 -> 0

    enter: 0 -> 0 -> 1

    Here is the code:

    import React, { useState, useEffect } from "react";
    import { useTransition, animated } from "react-spring";
    import ReactDOM from "react-dom";
    
    import "./styles.css";
    
    function App() {
      const [cards, set] = useState(["A"]);
    
      useEffect(() => {
        setInterval(() => {
          set(cards => (cards[0] === "A" ? "B" : "A"));
        }, 4000);
      }, []);
    
      const transitions = useTransition(cards, null, {
        from: { o: 0 },
        enter: { o: 1 },
        leave: { o: 2 },
        config: { duration: 2000 }
      });
      return transitions.map(({ item, key, props }) => (
        <div style={{ fontSize: "300px" }}>
          <animated.div
            style={{
              position: "absolute",
              opacity: props.o.interpolate([0, 0.5, 1, 1.5, 2], [0, 0, 1, 0, 0])
            }}
          >
            {item}
          </animated.div>
        </div>
      ));
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    working example: https://codesandbox.io/s/react-spring-staggered-transition-xs9wy