Search code examples
reactjsreact-nativereact-native-reanimated-v2

How to create a dynamic array of React hooks for an array of components


const AnimatedText = Animated.createAnimatedComponent(Text);

function Component({ texts }) {
  const [visitIndex, setVisitIndex] = React.useState(0);

  // can't create an array of shared value for each text
  // since useSharedValue is a hook, and that throws a warning
  const textScalesShared = texts.map((_) => useSharedValue(1));

  // can't create an array of animated style for each text
  // since useAnimatedStyle is a hook, and that throws a warning
  const animatedTextStyle = textScalesShared.map((shared) =>
    useAnimatedStyle(() => ({
      transform: [{ scale: shared.value }],
    }))
  );

  useEffect(() => {
    // code to reduce text scale one after another
    // it will loop over the array of textScaleShared values
    // passed to each component and update it
    if (visitIndex === texts.length) {
      return;
    }

    textScalesShared[visitIndex].value = withDelay(
      1000,
      withTiming(0.5, {
        duration: 1000,
      })
    );

    const timerId = setTimeout(() => {
      setVisitIndex((idx) => idx + 1);
    }, 1000);

    return () => {
      clearTimeout(timerId);
    };
  }, [visitIndex]);

  return texts.map((text, index) => {
    if (index <= visitIndex) {
      return (
        <AnimatedRevealingText
          key={index}
          fontSize={fontSize}
          revealDuration={revealDuration}
          style={animatedStylesShared[index]}
          {...props}
        >
          {text}
        </AnimatedRevealingText>
      );
    } else {
      return null;
    }
  });
}

I want to apply animated styles to an array of components, but since useSharedValue and useAnimatedStyle are both hooks, I am unable to loop over the prop and create a shared value and the corresponding style for each of the component.

How can I achieve the same?

EDIT: updated to add the full code.


Solution

  • You can create a component to handle the useSharedValue and useAnimatedStyle hooks for every item using the visitIndex value:

    AnimatedTextItem.js

    const AnimatedText = Animated.createAnimatedComponent(Text);
    
    const AnimatedTextItem = ({text, visited}) => {
      const textScaleShared = useSharedValue(1);
      const style = useAnimatedStyle(() => ({
        transform: [{ textScaleShared.value }],
      }));
    
      useEffect(()=> {
        if(visited) {
          textScaleShared.value = withDelay(
            1000,
            withTiming(0.5, {
              duration: 1000,
            });
          );
        }
      }, [visited]);
      
      return (<AnimatedText style={style}>{text}</AnimatedText>)
    }
    

    Component.js

    function Component({texts}) {
      const [visitIndex, setVisitIndex] = React.useState(0);
    
      useEffect(() => {
        // code to reduce text scale one after another
        // it will loop over the array of textScaleShared values
        // passed to each component and update it
        if (visitIndex === texts.length) {
          return;
        }
    
        const timerId = setTimeout(() => {
          setVisitIndex((idx) => idx + 1);
        }, revealDuration);
    
        return () => {
          clearTimeout(timerId);
        };
      }, []);
      return texts.map((text, index) => (<AnimatedTextItem text={text} visited={visitIndex === index}/>))
    }