'react The swipe animation will be performed twice

When you press the Circle button, the Box moves to the right and disappears from the screen.

Additional information (FW/tool version, etc.)
react
scss
Typescript
framer-motion

import "./style.scss";
import React, { FunctionComponent, useState } from "react";
import { useMotionValue, useTransform } from "framer-motion";
import { SwipeBox } from "./SwipeBox";
const App: FunctionComponent = () => {
  const [cards, setCards] = useState([
 



  const onClic = () => {
    animateCardSwipe({ x: -1400, y: 0 });
  };              <div
                style={{
                  width: "400px",
                  height: "300px",
                  background: `${card.background}`
                }}
              >
          ) : (
    </div>
  );
};

export default App;


Solution 1:[1]

The problem is that the animation is happening on mount and you're updating state twice inside the animateCardSwipe function:

const animateCardSwipe = (animation: { x: number, y: number }) => {
  setAnime({ ...anime, animation });

  setTimeout(() => {
    x.set(0);
    y.set(0);
    setCards([...cards.slice(0, cards.length - 1)]);
  }, 200);
};

I personally like to use a more imperative approach here for starting the animation using animate:

const animateCardSwipe = (animation: { x: number, y: number }) => {
  animate(x, animation.x, {
    duration: 1,
    onComplete: () => {
      x.set(0);
      y.set(0);
      setCards([...cards.slice(0, cards.length - 1)]);
    },
  });
};

This implementation also waits for the animation to complete before rearranging the cards.


Full example:

import React, { FunctionComponent, useState } from "react";
import {
  animate,
  useAnimation,
  useMotionValue,
  useTransform,
  MotionValue,
  motion,
} from "framer-motion";

interface Props {
  animate?: { x: number; y: number };
  style: {
x?: MotionValue;
y?: MotionValue;
zIndex: number;
rotate?: MotionValue;
  };
  card: { text: string; background: string };
}

const SwipeBox: React.FunctionComponent<Props> = (props) => {
  return (
<motion.div
  animate={props.animate}
  className="card"
  style={{
    ...props.style,
    background: "white",
    borderRadius: "8px",
    display: "grid",
    top: 0,
    left: 0,
    placeItems: "center center",
    position: "absolute",
    width: "100%",
  }}
  transition={{ duration: 1 }}
>
  {props.children}
</motion.div>
  );
};

const App: FunctionComponent = () => {
  const controls = useAnimation();
  console.log(controls);
  const [cards, setCards] = useState([
{ text: "Up or down", background: "red" },
{ text: "Left or right", background: "green" },
{ text: "Swipe me!", background: "gray" },
{ text: "Swipe me!", background: "purple" },
{ text: "Swipe me!", background: "yellow" },
  ]);

  const x = useMotionValue(0);
  const y = useMotionValue(0);

  const animateCardSwipe = (animation: { x: number; y: number }) => {
animate(x, animation.x, {
  duration: 1,
  onComplete: () => {
    x.set(0);
    y.set(0);
    setCards([...cards.slice(0, cards.length - 1)]);
  },
});
  };

  const [anime, setAnime] = useState<{ animation: { x: number; y: number } }>({
animation: { x: 0, y: 0 },
  });

  const rotate = useTransform(x, [-950, 950], [-30, 30]);

  const onClickLeft = () => {
animateCardSwipe({ x: 1400, y: 0 });
  };

  const onClickRight = () => {
animateCardSwipe({ x: -1400, y: 0 });
  };

  return (
<div style={{ display: "flex", flexDirection: "column" }}>
  <div>
    {cards.map((card, index) =>
      index === cards.length - 1 ? (
        <SwipeBox
          animate={anime.animation}
          card={card}
          key={index}
          style={{ x, y, zIndex: index, rotate: rotate }}
        >
          <div
            style={{
              width: "400px",
              height: "300px",
              background: `${card.background}`,
            }}
          >
            {card.text}
          </div>
        </SwipeBox>
      ) : (
        <SwipeBox
          card={card}
          key={index}
          style={{
            zIndex: index,
          }}
        >
          <div
            style={{
              width: "400px",
              height: "300px",
              background: `${card.background}`,
            }}
          >
            {card.text}
          </div>
        </SwipeBox>
      )
    )}
  </div>
  <div style={{ zIndex: 999 }}>
    <button onClick={onClickRight}>??</button>
    <button onClick={onClickLeft}>?</button>
  </div>
</div>
  );
};

export default App;

Codesandbox Demo

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1