I have been creating a custom Tinder-like slider component for one of my work projects, and I have the majority of my logic inside of the index.js
file. This has led to several functions being created making the component larger.
I was hoping too see if anyone has any suggestions on how to clean up this component and follow the best practices. I have been reading on the best practices regarding components and it has been suggested that components should be no longer than 150 lines of code.
Are there too many functions in my component?
It would be good to get other devs opinion on this.
import React, { useState, useRef, useEffect } from 'react';
import { Animated, PanResponder, Dimensions, StyleSheet, SafeAreaView, LayoutAnimation, UIManager, Text, View, Image } from 'react-native';
import BottomSwipeContainer from '../BottomSwipeContainer';
import NotLikedCardIcon from './NotLikedIcon';
import LikedCardIcon from './LikedCardIcon';
const SCREEN_WIDTH = Dimensions.get("window").width;
const SWIPE_THRESHOLD = 0.25 * SCREEN_WIDTH;
const SWIPE_OUT_DURATION = 250;
if (Platform.OS === 'android') {
if (UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
export const Deck = (props) => {
const pan = useRef(new Animated.ValueXY()).current;
const [index, setIndex] = useState(0);
useEffect(() => {
if(props.data) {
setIndex(0);
}
}, [props.data]);
const likeOpacity = pan.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [0, 0, 1],
extrapolate: 'clamp'
});
const notOpacity = pan.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0, 0],
extrapolate: 'clamp'
});
const panResponder = useRef(
PanResponder.create({
// position of item is started moving
onStartShouldSetPanResponder: () => true,
// callback function when the item is pressed/dragging
onPanResponderMove: (event, gesture) => {
// update the current position with gesture value
pan.setValue({ x: gesture.dx, y: gesture.dy });
},
// called anytime a item has been released after interaction
onPanResponderRelease: (event, gesture) => {
// if card was dragged more distance then swipe threshold
if(gesture.dx > SWIPE_THRESHOLD) {
forceSwipe('right');
} else if(gesture.dx < -SWIPE_THRESHOLD) {
forceSwipe('left');
} else {
resetPosition();
}
}
})
).current;
const getCardStyle = () => {
const rotate = pan.x.interpolate({
inputRange: [-SCREEN_WIDTH * 2, 0 ,SCREEN_WIDTH],
outputRange: ['-120deg', '0deg', '120deg']
});
return {
...pan.getLayout(),
transform: [{rotate}]
}
}
const forceSwipe = (direction) => {
const x = direction === 'right' ? SCREEN_WIDTH : -SCREEN_WIDTH;
Animated.timing(
pan,
{
toValue: { x, y: 0 },
duration: SWIPE_OUT_DURATION,
useNativeDriver: false
}
// add callback function to the .start method
// function will be called once the animation has been completed
).start(() => onSwipeComplete(direction));
}
const onSwipeComplete = (direction) => {
const item = props.data[index];
const { onSwipeLeft, onSwipeRight } = props;
direction === 'right' ? onSwipeRight(item) : onSwipeLeft(item);
// update position back to default
pan.setValue({ x: 0, y: 0 });
// update the index to the next element inside the array
setIndex(index => (index + 1));
// add layout animation effect after the next card is pushed up
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
}
const onSwipeBack = () => {
// update the index to the next element inside the array
pan.setValue({ x: 0, y: 0 });
if(index !== 0) {
setIndex(index => (index - 1));
}
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
}
const resetPosition = () => {
Animated.spring(
pan,
{
toValue: { x: 0, y: 0 },
useNativeDriver: false
}
).start();
}
const renderCards = () => {
if(index >= props?.data?.length) {
return props.renderNoMoreCards();
}
return props?.data?.map((item, i) => {
if(i < index) { return null };
if (i === index) {
return (
<Animated.View
key={item?._id}
{...panResponder.panHandlers}
style={[getCardStyle()]}
>
{props.renderCard(item)}
<LikedCardIcon likeIconOpacity={likeOpacity} />
<NotLikedCardIcon dislikeIconOpacity={notOpacity} />
</Animated.View>
);
}
return (
<Animated.View key={item?._id} style={styles.cardStyle}>
{props.renderCard(item)}
</Animated.View>
);
}).reverse();
}
return (
<SafeAreaView>
<View style={styles.container}>
{renderCards()}
</View>
{index != props?.data?.length &&
<BottomSwipeContainer
onSwipePrevious={onSwipeBack}
forceSwipe={forceSwipe}
task_id={props?.data[index]?._id}
distance={1}
/>
}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
marginTop: 25
},
cardStyle: {
position: 'absolute',
width: SCREEN_WIDTH,
}
});
This is the Deck component that is passed in via the parent component for reference.
<Deck
data={data?.taskSearch}
renderCard={renderCard}
renderNoMoreCards={renderNoMoreCards}
onSwipeRight={() => {}}
onSwipeLeft={() => {}}
forceSwipe={() => {}}
onSwipePrevious={() => {}}
/>