Step-by-Step Guide: Building a Custom Animated BottomSheet in React Native

This guide will walk you through creating a custom Bottom Sheet utilizing React-Native-Reanimated. It’s designed to enable users to pass children and props, complete with fluid animation. The process will be divided into manageable steps, each accompanied by clarifying explanations and relevant code. So, grab a cup of coffee, and let’s dive in.

Prerequisites

Make sure you have the following things already installed on your device.

  1. React Native Development Environment.
  2. Android Studio / Xcode ( for Mac device )

Let’s get started with the hope that you have a basic understanding of React Native concepts.

Setting Up the React Native Project

  • If you haven’t already created a project, you can follow the command to create a new React Native Project.
npx react-native init MyProject
  • Once the Project is installed, Navigate to the project directory.
cd MyProject
  • Install the necessary dependencies.
npm install react-native-reanimated react-native-gesture-handler
  • Add react-native-reanimated/plugin Plugin to your bable.config.js.
module.exports = {
presets: [
...
],
plugins: [
...
'react-native-reanimated/plugin', // HERE
],
};

Let’s delve into the code and discuss the functionality and role of each component.

Related read: A Complete Guide to Build the First React Native App

Creating the Component

First, let’s create the CustomBottomSheet Component, Where we will write the actual code of Custom BottomSheet using react-native-reanimated.

//CustomBottomSheet.js

import { Dimensions, StyleSheet, TouchableOpacity, View, Platform } from 'react-native';
import React, { useCallback, useImperativeHandle } from 'react';
import Animated, {
Extrapolate,
interpolate,
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated';
import SvgIcon from '../SvgIcon';
import SvgIcons from '../../res/svgs';
import Colors from '../../theme/Colors';

const { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get('window');

const MAX_TRANSLATE_Y = -SCREEN_HEIGHT;

const CustomBottomSheet = React.forwardRef(({ children, hideBottomSheet, showClose = true }, ref) => {
const translateY = useSharedValue(0);
const active = useSharedValue(false);

const scrollTo = useCallback(destination => {
'worklet';
active.value = destination !== 0;

translateY.value = withSpring(destination, { damping: 50 });
}, []);

const handleOnHideBottomSheet = () => {
scrollTo(0);
hideBottomSheet();
};

const isActive = useCallback(() => {
return active.value;
}, []);

useImperativeHandle(ref, () => ({ scrollTo, isActive }), [scrollTo, isActive]);

const context = useSharedValue({ y: 0 });

const rBottomSheetStyle = useAnimatedStyle(() => {
const borderRadius = interpolate(
translateY.value,
[MAX_TRANSLATE_Y + 50, MAX_TRANSLATE_Y],
[25, 5],
Extrapolate.CLAMP,
);

return {
borderRadius,
transform: [{ translateY: translateY.value }],
};
});

return (
<Animated.View style={[styles.bottomSheetContainer, rBottomSheetStyle]}>
{showClose && (
<TouchableOpacity onPress={handleOnHideBottomSheet} style={styles.closeIcon}>
<SvgIcon name={SvgIcons.blackCloseIcon} height={38} width={38} />
</TouchableOpacity>
)}

<View style={styles.content}>{children}</View>
</Animated.View>
);
});

const styles = StyleSheet.create({
bottomSheetContainer: {
height: SCREEN_HEIGHT,
width: SCREEN_WIDTH,
position: 'absolute',
top: SCREEN_HEIGHT + 50,
},
closeIcon: {
alignItems: 'center',
marginBottom: 20,
},
content: {
flex: 1,
backgroundColor: Colors.white,
borderRadius: 25,
...Platform.select({
android: {
elevation: 12,
},
ios: {
shadowColor: Colors.black,
shadowOffset: {
width: 5,
height: 5,
},
shadowOpacity: 0.5,
shadowRadius: 6.27,
},
}),
},
});

export default React.memo(CustomBottomSheet);

Let’s delve into the functionality of this component and what it’s truly accomplishing.

1. Animation Setup:

  • The component utilizes react-native-reanimated to manage animations. It initializes two shared values: translateY and active.
  • translateY represents the vertical translation of the bottom sheet, while active is a boolean flag indicating whether the bottom sheet is currently visible or hidden.

2. Scrolling Functionality:

  • The scrollTo function is responsible for smoothly animating the bottom sheet to a specified destination.
  • When invoked, it sets the active flag based on the destination value and animates the translateY value using withSpring to create a spring-like animation effect.

3. Handling Close Event:

  • The handleOnHideBottomSheet function is triggered when the close button is pressed.
  • It scrolls the bottom sheet back to its initial position (destination 0) and then calls the hideBottomSheet function passed as a prop to hide the bottom sheet entirely.

4. Ref Handling:

  • The useImperativeHandle hook exposes the scrollTo and isActive functions via a ref, allowing external components to control the bottom sheet’s behavior.
  • This enables parent components to programmatically open, close, or check the state of the bottom sheet.

5. Animated Styles:

  • The useAnimatedStyle hook calculates dynamic styles for the bottom sheet based on the animated translateY value.
  • It interpolates the borderRadius and translateY properties to create smooth transitions as the bottom sheet moves.

6. Rendering:

  • The component renders an Animated.View with the calculated animated styles.
  • If the showClose prop is true, it renders a close button (using SvgIcon) inside a TouchableOpacity component.
  • The content passed as children are rendered inside a View component, forming the main body of the bottom sheet.

7. Styles:

  • Styles are defined using StyleSheet.create to ensure consistency and performance.
  • The bottomSheetContainer style sets the initial position of the bottom sheet off-screen, ensuring it’s not visible until it’s animated into view.

8. Export: 

  • The CustomBottomSheet component is exported as the default export, wrapped in React.memo to optimize performance by memoizing the component.

In summary, the CustomBottomSheet component encapsulates the logic for creating a customizable bottom sheet with smooth animations in React Native. It provides a flexible and reusable solution for displaying additional content within a mobile app’s UI, enhancing user experience and interaction.

Related read: Setting up Storybook in React Native and Creating a Component

How to Use this Component

In essence, the process is as simple as drinking water. To achieve this, all you need to do is import this particular component into the desired location within your code. Once you’ve done this, the next step is to pass the children or subcomponents, that you wish to render onto the screen. By following these steps, you’ll be able to efficiently display the content you want within your application.

Let’s dive deeper into how exactly this is made possible, exploring the intricacies of this process to gain a comprehensive understanding of it.

import React, { useEffect, useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import CustomBottomSheet from './CustomBottomSheet';

const BottomSheetComponent = () => {
const bottomSheetRef = useRef(null);

useEffect(() => {
bottomSheetRef.current?.scrollTo(-400);
}, []);

/**
* Function to handle hiding the bottom sheet.
*/
const handleOnHideBottomSheet = () => {
bottomSheetRef.current?.scrollTo(0);
};

return (
<CustomBottomSheet ref={bottomSheetRef} hideBottomSheet={handleOnHideBottomSheet}>
<View style={styles.container}>
<Text style={styles.titleStyle}>{title}</Text>
</View>
</CustomBottomSheet>
);
};

export default BottomSheetComponent;

const styles = StyleSheet.create({
container: {
flex: 1,
},
titleStyle: {
margin: 15,
},
});

The BottomSheetComponent is a demonstration of how to use the CustomBottomSheet in a practical scenario. It utilizes the useRef hook to create a reference, bottomSheetRef, which is assigned to the CustomBottomSheet component. This allows for control over the bottom sheet’s behavior from within the BottomSheetComponent. The useEffect hook is then used to automatically scroll the bottom sheet into view when the component mounts, simulating a typical use-case where a bottom sheet might open upon a specific event or user action.

Additionally, it demonstrates how to handle the close event by defining a handleOnHideBottomSheet function that scrolls the bottom sheet out of view when called. The children’s prop is used to pass the content to be displayed in the bottom sheet, in this case, a simple View containing a Text element.

coma

Conclusion

In conclusion, this guide provides a detailed walkthrough on building a custom animated bottom sheet component in React Native. The steps are designed to be easy to follow, with explanations provided for each part of the process. This component offers a great way to enhance the user experience of your app by providing a smooth and customizable interface for additional content.

Happy coding!

Keep Reading

Keep Reading

Launch Faster with Low Cost: Master GTM with Pre-built Solutions in Our Webinar!

Register Today!
  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?