DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on

Enhancing Animation Experience in React Native with Context API

Image description

Creating a dynamic user experience in mobile applications often involves making components react to the user’s scroll behavior. From hiding headers when scrolling down to providing a floating scroll-to-top button and applying smooth fade effects, these interactions enhance usability and create a modern feel.

In this guide, we’ll walk through building a centralized scroll management system in React Native using FlatList, react-native-reanimated, and context. We’ll implement the following features:

  1. An animated header that hides on scroll.
  2. A floating scroll-to-top button that appears when the user scrolls down.

Why Use a Shared Scroll Context?

Using a shared scroll context allows us to:

  • Manage scroll position centrally, making it accessible to any component that needs it.
  • Animate multiple components in sync based on scroll events.
  • Easily implement a scroll-to-top functionality that can be triggered from any part of the app.

Required Libraries

  • React Native Reanimated: Provides efficient and smooth animations.
  • React Context: Allows us to share the scroll state across components.

Step 1: Setting Up the Shared Scroll Context

We’ll start by creating a SharedStateContext to store two values:

  • scrollY: Tracks whether the user is scrolling down.
  • scrollYGlobal: Tracks the overall scroll position for global animations.

Creating the Shared Context

import React, { createContext, useContext } from 'react';
import { useSharedValue, withTiming } from 'react-native-reanimated';

const SharedStateContext = createContext(undefined);

export const SharedStateProvider = ({ children }) => {
  // Create shared values for scroll positions
  const scrollY = useSharedValue(0); // Animate scrollY value based on scroll direction
  const scrollYGlobal = useSharedValue(0); // Get the current vertical scroll position

  // Function to scroll to the top
  const scrollToTop = React.useCallback(() => {
    scrollY.value = withTiming(0, { duration: 300 }); // Animate scrollY to 0 over 300ms
    scrollYGlobal.value = withTiming(0, { duration: 300 }); // Animate scrollYGlobal to 0 over 300ms
  }, [scrollY, scrollYGlobal]);

  // Memoize the context value to avoid unnecessary re-renders
  const value = React.useMemo(
    () => ({ scrollToTop, scrollY, scrollYGlobal }),
    [scrollY, scrollYGlobal, scrollToTop]
  );

  return (
    // Provide the shared state to children components
    <SharedStateContext.Provider value={value}>{children}</SharedStateContext.Provider>
  );
};
export const useSharedState = () => {
  const context = useContext(SharedStateContext);
  if (context === undefined) {
    throw new Error('useSharedState must be used within a SharedStateProvider');
  }
  return context;
};
Enter fullscreen mode Exit fullscreen mode

Explanation

  • scrollY: A shared value that tracks whether the user is scrolling down (1) or up (0), based on scroll events.
  • scrollYGlobal: Tracks the overall scroll position of our FlatList.
  • scrollToTop: Resets both scrollY and scrollYGlobal values to zero, providing a smooth scroll-to-top action.

Wrapping the App with SharedStateProvider

Wrap your main app component (or specific screen components) with SharedStateProvider to make scrollY, scrollYGlobal, and scrollToTop accessible across components.

import React from 'react';
import MainScreen from './MainScreen';
import { SharedStateProvider } from './SharedContext';

const App = () => (
  <SharedStateProvider>
    <MainScreen />
  </SharedStateProvider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

Step 2: Creating an Animated Header Component

The Header component will slide up and hide when the user scrolls down and reappear when scrolling up. This behavior is controlled by scrollY, which detects the scroll direction.

import React from 'react';
import { StyleSheet, Text } from 'react-native';
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
import { useSharedState } from './SharedContext';

const Header = () => {
  const { scrollY } = useSharedState();

  const animatedHeaderStyle = useAnimatedStyle(() => ({
    transform: [
      {
        translateY:
          scrollY.value === 1
            ? withTiming(-100, { duration: 300 })
            : withTiming(0, { duration: 300 }),
      },
    ],
  }));

  return (
    <Animated.View style={[styles.header, animatedHeaderStyle]}>
      <Text style={styles.headerText}>Animated Header</Text>
    </Animated.View>
  );
};

export default Header;

const styles = StyleSheet.create({
  header: {
    paddingTop: 40,
    height: 100,
    backgroundColor: '#6200ee',
    justifyContent: 'center',
    alignItems: 'center',
    position: 'absolute',
    width: '100%',
    top: 0,
    zIndex: 1,
  },
  headerText: {
    color: '#fff',
    fontSize: 18,
  },
});
Enter fullscreen mode Exit fullscreen mode

Explanation

  • translateY animation: Controls the position of the header based on scrollY. When scrollY is 1 (scrolling down), the header slides up out of view; otherwise, it remains visible.
  • withTiming: Smoothly animates the transition, providing a polished user experience.

Step 3: Implementing the Scroll-To-Top Feature with FlatList

Next, we’ll set up MainScreen with a FlatList, Header, and a floating scroll-to-top button. This screen will track the scroll position and trigger animations when reaches 100px during scroll.

MainScreen Component

import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, { withTiming } from 'react-native-reanimated';
import Header from './Header';
import ScrollToTopButton from './ScrollToTopButton';
import { useSharedState } from './SharedContext';

const MainScreen = () => {
  const { scrollY, scrollYGlobal, scrollToTop } = useSharedState();
  const flatListRef = useRef(null);

  const handleScroll = (event) => {
    const currentScrollY = event.nativeEvent.contentOffset.y;

    const isScrollingDown = currentScrollY > 50;
    scrollY.value = isScrollingDown
      ? withTiming(1, { duration: 300 })
      : withTiming(0, { duration: 300 });
    scrollYGlobal.value = currentScrollY;
  };

  const handleScrollToTop = () => {
    scrollToTop();
    flatListRef.current?.scrollToOffset({
      offset: 0,
      animated: true,
    });
  };

  const data = Array.from({ length: 50 }, (_, i) => ({ id: String(i), text: `Item ${i + 1}` }));

  return (
    <View style={styles.container}>
      <Header />
      <Animated.FlatList
        ref={flatListRef}
        data={data}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View style={styles.item}>
            <Text>{item.text}</Text>
          </View>
        )}
        onScroll={handleScroll}
        scrollEventThrottle={16}
        contentContainerStyle={{ paddingTop: 100 }}
      />
      <ScrollToTopButton onPress={handleScrollToTop} />
    </View>
  );
};

export default MainScreen;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  item: {
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
});
Enter fullscreen mode Exit fullscreen mode

Step 4: Floating Scroll-To-Top Button with Opacity Control

The ScrollToTopButton component only appears when the user scrolls down beyond 100 pixels. Its opacity fades in, adding a subtle yet dynamic touch to the UI.

import React from 'react';
import { StyleSheet, Text, TouchableOpacity } from 'react-native';
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
import { useSharedState } from './SharedContext';

const ScrollToTopButton = ({ onPress }) => {
  const { scrollYGlobal } = useSharedState();

  const animatedStyle = useAnimatedStyle(() => ({
    opacity: scrollYGlobal.value > 100 ? 1 : 0,
    transform: [{ translateY: scrollYGlobal.value > 100 ? 0 : 100 }],
  }));

  return (
    <Animated.View style={[styles.buttonContainer, animatedStyle]}>
      <TouchableOpacity onPress={onPress} style={styles.button}>
        <Text style={styles.buttonText}>Top</Text>
      </TouchableOpacity>
    </Animated.View>
  );
};

export default ScrollToTopButton;

const styles = StyleSheet.create({
  buttonContainer: {
    position: 'absolute',
    bottom: 30,
    right: 20,
  },
  button: {
    backgroundColor: '#6200ee',
    padding: 20,
    borderRadius: 30,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
  },
});

Enter fullscreen mode Exit fullscreen mode

Final Thoughts

In this tutorial, we created an advanced scroll management system with a dynamic React Native UI that includes:

  1. An animated header that hides when the user scrolls down.
  2. A floating scroll-to-top button that fades in after scrolling down.

Using react-native-reanimated and context, this setup allows you to manage scroll interactions and animations centrally, providing a seamless and responsive user experience. This approach is perfect for building polished, modern UIs that respond intuitively to user interactions.

Top comments (0)