Search code examples
reactjstypescriptreact-spring

React Spring translate animation not working and clicking on a list item seems to be delayed


I have a simple list of options in a menu like so:

Option 1
Option 2
Option 3

When the user clicks on an option, there should be a highlighted bar that shows which one they selected. And when the user clicks on different options, the highlighted bar should slide up and down depending on what they chose. I'm trying to use react-spring, but I can't seem to get the animation and clicking behavior to happen properly.

With my current code, the highlighted bar does not slide up and down; it just shows and hides upon user selection. And clicking on an option once does not put the highlighted bar on it, instead, I have to click twice for it to show up correctly on the selected option.

Help is appreciated! This is my first time using react-spring so I'm a bit lost on this.

Below is the code snippet for the animations and rendering the component:

  const [currentIndex, setCurrentIndex] = useState<number>(0);
  const [previousIndex, setPreviousIndex] = useState<number>(0);

  const onClick = (name: string, index: number) => {
    setPreviousIndex(currentIndex);
    setCurrentIndex(index);
    setSpring(fn());
  };

  // Spring animation code
  const fn = () => (
    {
      transform: `translateY(${currentIndex * 52}px)`,
      from: {
        transform: `translateY(${previousIndex * 52}px)`,
      },
    }
  );
  const [spring, setSpring] = useState<any>(useSpring(fn()));

  // Rendering component
  return (
    <div>
      {options.map((option, index) => (
        <>
          {currentIndex === index && <animated.div style={{...spring, ...{ background: 'orange', height: 52, width: '100%', position: 'absolute', left: 0, zIndex: 1}}}></animated.div>}
          <div onClick={() => onClick(option.name, index)}>
            <TextWithIcon icon={currentIndex === index ? option.filledIcon : option.outlineIcon} text={option.name} />
          </div>
        </>
      ))}
    </div>
  );

And here is the custom component, TextWithIcon:

// Interfaces
interface TextWithIconProps {
  containerStyle?: Record<any, any>;
  icon: ReactElement;
  text: string;
  textStyle?: Record<any, any>;
}

// TextWithIcon component
const TextWithIcon: React.FC<TextWithIconProps> = ({ containerStyle, icon, text, textStyle}) => {
  return (
    <div id='menu-items' style={{...styles.container, ...containerStyle}}>
      {icon}
      <Text style={{...styles.text, ...textStyle}}>{text}</Text>
    </div>
  )
};

Solution

  • You have to set the duration property of the config property inside the useSpring's parameter to a value. Try the following:

    const [currentIndex, setCurrentIndex] = useState<number>(0);
      const [previousIndex, setPreviousIndex] = useState<number>(0);
    
      const onClick = (name: string, index: number) => {
        setPreviousIndex(currentIndex);
        setCurrentIndex(index);
        setSpring(fn());
      };
    
      // Spring animation code
      const fn = () => (
        {
          transform: `translateY(${currentIndex * 52}px)`,
          from: {
            transform: `translateY(${previousIndex * 52}px)`,
          },
    config: {
    duration: 1250 //this can be a different number
    }
        }
      );
      const [spring, setSpring] = useState<any>(useSpring(fn()));
    
      // Rendering component
      return (
        <div>
          {options.map((option, index) => (
            <>
              {currentIndex === index && <animated.div style={{...spring, ...{ background: 'orange', height: 52, width: '100%', position: 'absolute', left: 0, zIndex: 1}}}></animated.div>}
              <div onClick={() => onClick(option.name, index)}>
                <TextWithIcon icon={currentIndex === index ? option.filledIcon : option.outlineIcon} text={option.name} />
              </div>
            </>
          ))}
        </div>
      );
    

    References:

    StackOverflow. Animation duration in React Spring.https://stackoverflow.com/a/54076843/8121551. (Accessed August 23, 2021).