title: React (Native) Higher Order Component with animation logic and progress pass-through
body: <p>Currently, I have multiple Views that can be dismissed by a drag-down gesture. When dragging down, the view gets smaller until it reaches a point where I call <code>goBack()</code> on the navigator, dismissing it completely.</p>
  
  <p>Now since I don't want to implement this logic in many files over and over again, I've tried creating an HoC to write the dismiss animation logic once and keep my other components focused on their specific tasks. I also wanted to pass a <code>dismissAnimationProgress</code> value through to my wrapped components, so they can handle some extra logic while the user drags down (such as fading some things out).</p>
  
  <p>This is my <code>DismissableView.tsx</code>:</p>
  
  <pre class="lang-js prettyprint-override"><code>export interface DismissableViewProps {
      dismissProgress: Reanimated.Value&lt;number&gt;;
  }
  
  export default function DismissableView&lt;TView extends React.Component&lt;TViewProps&gt;, TViewProps extends DismissableViewProps&gt;(
      WrappedComponent: TView,
      props: TViewProps,
  ): React.ReactElement {
      const { navigation, ...passThroughProps } = props;
  
      const navigateBack = useMemo(() =&gt; navigation.pop(), [navigation]);
      const gestureHandler = usePanGestureHandler();
      const dismissAnimationProgress = useValue(0); // Animation from 0 -&gt; 1, where 1 is dismiss.
      const cardBorderRadius = useMemo(() =&gt; {
          return Reanimated.interpolate(dismissAnimationProgress, {
              inputRange: [0, 1],
              outputRange: [0, 30],
              extrapolate: Extrapolate.CLAMP,
          });
      }, [dismissAnimationProgress]);
      const viewScale = useMemo(() =&gt; {
          return Reanimated.interpolate(dismissAnimationProgress, {
              inputRange: [0, 1],
              outputRange: [1, 0.8],
              extrapolate: Extrapolate.CLAMP,
          });
      }, [dismissAnimationProgress]);
      useCode(
          () =&gt; [
              cond(eq(gestureHandler.state, State.ACTIVE), [
                  set(
                      dismissAnimationProgress,
                      Reanimated.interpolate(gestureHandler.translation.y, {
                          inputRange: [0, SCREEN_HEIGHT * 0.2],
                          outputRange: [0, 1],
                      }),
                  ),
              ]),
              cond(eq(gestureHandler.state, State.END), [
                  set(dismissAnimationProgress, timing({ from: dismissAnimationProgress, to: 0, duration: 200, easing: Easing.out(Easing.ease) })),
              ]),
              cond(greaterOrEq(dismissAnimationProgress, 1), [call([], navigateBack)]),
          ],
          [],
      );
      const viewStyle = useMemo(() =&gt; {
          return { transform: [{ scale: viewScale, borderRadius: cardBorderRadius }] };
      }, [viewScale, cardBorderRadius]);
  
      return (
          &lt;PanGestureHandler {...gestureHandler.gestureHandler}&gt;
              &lt;Reanimated.View style={viewStyle}&gt;
                  &lt;WrappedComponent {...passThroughProps} navigation={navigation} dismissAnimationProgress={dismissAnimationProgress} /&gt;
              &lt;/Reanimated.View&gt;
          &lt;/PanGestureHandler&gt;
      );
  }
  </code></pre>
  
  <p>Which does not work as expected, since the component's properties won't get passed through (because they won't get supplied in the parameter), and the dismissAnimationProgress prop also won't get set.</p>
  
  <p>In my Navigator (<code>@react-navigation/stack</code>) I'm using it like this:</p>
  
  <pre class="lang-js prettyprint-override"><code>import ProfileScreen from '../screens/social/ProfileScreen';
  import DismissableView from '../components/DismissableView';
  
                                   // No parameter for props here
  const DismissableProfileScreen = DismissableView(ProfileScreen);
  
                                    // Props include { route, navigation } for "ProfileScreen"
  const Stack = createStackNavigator&lt;MainStackNavigatorProps&gt;();
  export default function MainStackNavigator(): JSX.Element {
    return ( 
      &lt;Stack.Navigator&gt;
        &lt;Stack.Screen name="ProfileScreen" component={DismissableProfileScreen} /&gt;
      &lt;/Stack.Navigator&gt; );
  }
  </code></pre>