I've been trying to develop a react native app and I'm stuck here . I want to decompose onboarding component. Here is what I've done . Is it oke or what else can I do more readeble code ? I've divided Slider and SubSlider component but I think it isn't enough .Can I use component instead of functions Any suggestion will help me . What are the best practices in this case ? Thanks
const renderScrollView = (scroll, width: number, x: Animated.Value) => {
return (
<Animated.ScrollView showsHorizontalScrollIndicator={false} ref={scroll} pagingEnabled
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x } } }], {
useNativeDriver: true,
})}
horizontal
>
{slides.map((data, index) => {
return (
<Slider key={index}>
<SliderImage src={data.src} />
<Box paddingHorizontal="l">
<SliderTitle>{data.label}</SliderTitle>
<SliderText>{data.description}</SliderText>
</Box>
</Slider>
);
})}
</Animated.ScrollView>
);
};
const renderSubSlider = (width: number,x: Animated.Value) => {
return (
<SubSlider>
<SubSliderItem>
{slides.map((_, index) => {
return (
<Dot
key={index + 1}
currentIndex={Animated.divide(x, width)}
{...{ index }}
/>
);
})}
</SubSliderItem>
</SubSlider>
);
};
const Onboarding = React.memo(({ onDone }: OnboardingProps) => {
const { width } = useDimensions().window;
const [completed, setCompleted] = useState<boolean>(false);
const scroll = useRef(null);
const x = React.useRef<Animated.Value>(new Animated.Value(0)).current;
React.useEffect(() => {
x.addListener(({ value }) => {
if (Math.ceil(value / width) === slides.length - 1) {
setCompleted(true);
} else {
setCompleted(false);
}
});
return () => x.removeAllListeners();
}, []);
return (
<Box flex={1} backgroundColor="mainBackground">
<Box flex={3}>{renderScrollView(scroll, width, x)}</Box>
{renderSubSlider(width, x)}
</Box>
);
});
Related
I am new to React Native Animation & I was following a tutorial & I did exactly what he was teaching but I failed to get the desired result. I am trying to develop a multipage onboarding screen with a svg animation. The problem is that it works fine but the moment I reach the last item in my flatlist, it doesn't update the index and hence the animation is not completed for the last array item.
Below is the relevant code:
RegisterationScreen:
const data = [
<EmailComponent />,
<PasswordComponent />,
<DPComponent />,
<AgeComponent />,
];
const RegisterScreen = () => {
const scrollX = useRef(new Animated.Value(0)).current;
const [currentIndex, setCurrentIndex] = useState(0);
const slidesRef = useRef(null);
const viewableItemsChanged = useCallback(({viewableItems}) => {
console.log(viewableItems[0]);
setCurrentIndex(viewableItems[0].index);
}, []);
const scrollTo = () => {
if (currentIndex < data.length - 1) {
slidesRef.current.scrollToIndex({index: currentIndex + 1});
} else {
console.log('Last Item!');
}
};
const goBack = () => {
if (currentIndex === 0) {
return;
}
slidesRef.current.scrollToIndex({index: currentIndex - 1});
};
// console.log(currentIndex);
return (
<RegisterationStateProvider>
<View style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
<SafeAreaView style={styles.back}>
<TouchableOpacity onPress={goBack} activeOpacity={0.6}>
<AntDesign name="arrowleft" size={32} color="black" />
</TouchableOpacity>
</SafeAreaView>
<View style={{flex: 3}}>
<FlatList
ref={slidesRef}
scrollEnabled
data={data}
keyExtractor={(_, index) => 'key' + index}
renderItem={({item}) => item}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
bounces={false}
onScroll={
Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}],
{useNativeDriver: false},
)}
scrollEventThrottle={32}
onViewableItemsChanged={viewableItemsChanged}
/>
</View>
<Dot data={data} scrollX={scrollX} />
<NextButton
scrollTo={scrollTo}
percentage={(currentIndex + 1) * (100 / data.length)}
/>
</View>
</RegisterationStateProvider>
);
};
NextButton:
const NextButton = ({percentage, scrollTo}) => {
const size = 100;
const strokeWidth = 4;
const center = size / 2;
const radius = size / 2 - strokeWidth / 2;
const circumference = 2 * Math.PI * radius;
// console.log(percentage);
const progressAnimation = useRef(new Animated.Value(0)).current;
const progressRef = useRef(null);
const animation = toValue => {
return Animated.timing(progressAnimation, {
toValue,
duration: 250,
useNativeDriver: false,
}).start();
};
useEffect(() => {
animation(percentage);
}, [percentage]);
useEffect(() => {
progressAnimation.addListener(
value => {
const strokeDashoffset =
circumference - (circumference * value.value) / 100;
if (progressRef?.current) {
progressRef.current.setNativeProps({
strokeDashoffset,
});
}
},
[percentage],
);
return () => {
progressAnimation.removeAllListeners();
};
}, []);
return (
<View style={styles.container}>
<Svg width={size} height={size}>
<G rotation="-90" origin={center}>
<Circle
stroke="#E6E7E8"
cx={center}
cy={center}
r={radius}
strokeWidth={strokeWidth}
/>
<Circle
ref={progressRef}
stroke="#FF5864"
cx={center}
cy={center}
r={radius}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={circumference}
/>
</G>
</Svg>
<TouchableOpacity
disabled={false}
onPress={scrollTo}
style={styles.button}
>
<AntDesign name="arrowright" size={35} color="#fff" />
</TouchableOpacity>
</View>
);
};
I cannot find the error/bug in the code, everything looks fine because when I check if (currentIndex < data.length - 1) in scrollTo function, it should render for index=3 because 3<4 is true obviously but unfortunately it doesn't work and no error in console either.
Below is the output of console for console.log(currentIndex); & console.log(percentage);
LOG {"index": 0, "isViewable": true, "item": <EmailComponent />, "key": "key0"}
LOG {"index": 0, "isViewable": true, "item": <EmailComponent />, "key": "key0"}
LOG {"index": 1, "isViewable": true, "item": <PasswordComponent />, "key": "key1"}
LOG {"index": 1, "isViewable": true, "item": <PasswordComponent />, "key": "key1"}
LOG {"index": 2, "isViewable": true, "item": <DPComponent />, "key": "key2"}
LOG 0
LOG 25
LOG 1
LOG 50
LOG 2
LOG 75
As you can see that even though I am at the last item, the animation is still at (75%) 270° & not full complete i.e. (100%) 360° & {index:3} is not logging in console even though I'm at last item of data.
onViewableItemsChanged is not being triggered on the last item in the FlatList. Lower the viewAreaCoveragePercentThreshold in the viewabilityConfig property https://reactnative.dev/docs/flatlist#viewabilityconfig
I am trying to display ActivityIndicator when the list reaches the bottom. However, currently it loads without showing the spinner. Am I doing this wrongly?
And sometimes when it shows the ActivityIndicator , it will call loadShops twice resulting in calling the API twice.
const dispatch = useDispatch();
const shops = useSelector((state) => state.shops.availableShops);
const [shopLimit, setShopLimit] = useState(10);
const [skip, setSkip] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const loadShops = async () => {
setIsLoading(true);
try {
await dispatch(shopsActions.getShopsList(skip, shopLimit));
} catch (error) {
throw error;
}
setIsLoading(false);
};
useEffect(() => {
console.log("useEffect called!!");
if (shopLimit <= 30) {
loadShops();
}
}, [shopLimit]);
return (
<View style={styles.container}>
{console.log("rendering, ", `isLoadingState: ${isLoading}`)}
<SearchBarCmp
containerStyle={styles.containerStyle}
inputContainerStyle={styles.inputContainerStyle}
placeholder={"Search by shop name or location..."}
/>
<FlatList
data={shops}
renderItem={(shop) => (
<ShopItem
imageUrl={shop.item.url}
shopName={shop.item.name}
pickupHour={shop.item.pickup_hour}
originalPrice={shop.item.original_price}
discountedPrice={shop.item.discounted_price}
/>
)}
keyExtractor={(item, index) => index.toString()}
onEndReached={() => {
setShopLimit((prevState) => prevState + 10);
}}
onEndReachedThreshold={0.01}
scrollEventThrottle={150}
ListFooterComponent={() => {
if (isLoading) {
return (
<View style={styles.footerContainer}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
);
} else {
return null
}
}}
/>
</View>
);
use setIsLoading(false); in finally
try{}
catch(error){}
finally { setIsLoading(false); }
Your loader is set to false before the try catch completes
I will need a helping hand to edit this page. i have all the elements but i need help styling.
I would like to have the camera (the image you see is the typical emulator camera, that's why it makes an image) in full screen and from above at the top, the message in red and the 'autocomplete.
If you want, to explain better, I would like to respect the image below: autocomplete at the top left above the camera in full screen.
would it be possible for you to help me, I'm getting a little confused. I tried to do a snack but failed. I will add it later if i can.
const autocompletes = [...Array(10).keys()];
const apiUrl = "https://5b927fd14c818e001456e967.mockapi.io/branches";
class Tickets extends Component {
constructor(props) {
super(props);
this.state = {
Press: false,
hasCameraPermission: null,
reference: '',
lastScannedUrl:null,
displayArray: []
};
}
initListData = async () => {
let list = await getProductByRef(1);
if (list) {
this.setState({
displayArray: list,
reference: list.reference
});
}
// console.log('reference dans initListData =', list.reference)
};
async UNSAFE_componentWillMount() {
this.initListData();
// console.log('reference dans le state =', this.state.reference)
};
componentDidMount() {
this.getPermissionsAsync();
}
getPermissionsAsync = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === "granted" });
};
_onPress_Scan = () => {
this.setState({
Press: true
});
}
handleBarCodeScanned = ({ type, data }) => {
this.setState({ Press: false, scanned: true, reference: data });
this.props.navigation.navigate('ProductDetails', {reference : parseInt(this.state.state.reference)})
};
renderBarcodeReader = () => {
const { hasCameraPermission, scanned } = this.state;
if (hasCameraPermission === null) {
return <Text>{i18n.t("scan.request")}</Text>;
}
if (hasCameraPermission === false) {
return <Text>{i18n.t("scan.noaccess")}</Text>;
}
return (
<View
style={{
flex: 1,
...StyleSheet.absoluteFillObject
}}
>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : this.handleBarCodeScanned}
style={{ flex:1, height:'100%', ...StyleSheet.absoluteFillObject}}
/>
{scanned && (
<Button
title={"Tap to Scan Again"}
onPress={() => this.setState({ scanned: false })}
/>
)}
</View>
);
}
handleSelectItem(item, index) {
const {onDropdownClose} = this.props;
onDropdownClose();
console.log(item);
}
render() {
const { hasCameraPermission, scanned, Press } = this.state;
let marker = null;
const {scrollToInput, onDropdownClose, onDropdownShow} = this.props;
// console.log('displayArray', this.state.displayArray, 'reference', this.state.displayArray.reference)
return (
<View style={styles.container}>
{Press ? (
<View style={{flex:1}}>
<View style={styles.dropdownContainerStyle}>
<Autocomplete
key={shortid.generate()}
containerStyle={styles.autocompleteContainer}
inputStyle={{ borderWidth: 1, borderColor: '#F78400'}}
placeholder={i18n.t("tickets.warning")}
pickerStyle={styles.autocompletePicker}
scrollStyle={styles.autocompleteScroll}
scrollToInput={ev => scrollToInput(ev)}
handleSelectItem={(item, id) => this.handleSelectItem(item, id)}
onDropdownClose={() => onDropdownClose()}
onDropdownShow={() => onDropdownShow()}
fetchDataUrl={apiUrl}
minimumCharactersCount={2}
highlightText
valueExtractor={item => item.name}
rightContent
rightTextExtractor={item => item.properties}
/>
</View>
{this.renderBarcodeReader()}
</View>
) : (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button
color="#F78400"
title={i18n.t("scan.scan")}
onPress={this._onPress_Scan}>
</Button>
</View>
)}
</View>
);
}
}
export default Tickets;
This gives me (after pressing the button) :
SNACK CODE TEST
I notice You are using a component from Expo called BarCodeScanner
There's a github issue open about the fact that this component is not possible to be styled for full screen: https://github.com/expo/expo/issues/5212
However one user proposes a good solution: replace BarCodeScanner with Camera and use barcodescannersettings
Here's a link for the answer on the gitHub issue: https://github.com/expo/expo/issues/5212#issuecomment-653478266
Your code should look something like:
renderBarcodeReader = () => {
const { hasCameraPermission, scanned } = this.state;
[ ... ] // the rest of your code here
return (
<View
style={{
flex: 1,
...StyleSheet.absoluteFillObject
}}
>
<Camera
onBarCodeScanned={scanned ? undefined : this.handleBarCodeScanned}
style={{ flex:1}}
barCodeScannerSettings={{
barCodeTypes: [BarCodeScanner.Constants.BarCodeType.qr],
}}
/>
</View>
);
}
I'm trying to migrate to react navigation 5, but I get an error where I use the DrawerItemList in place of the DrawerNavigatorItems.
My code used to look like this:
const MainNavigator = createDrawerNavigator(
// e.g.
Winners: {
screen: WinnersNavigator,
navigationOptions: {
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Νικητές</BoldText>
),
drawerIcon: tabInfo => {
return (
<View style={NavigationStyles.winners}>
<FontAwesome
name="users"
size={iconMultiplier / 10}
color={tabInfo.tintColor}
/>
</View>
);
}
}
},
// ... more screens
contentComponent: props => {
const dispatch = useDispatch();
// This is for showing the Admin screen link, if user is an admin.
const userIdExists = useSelector(state => state.auth.userId);
return (
<View style={{ flex: 1 }}>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
{/* These are the default drawer items */}
// THE PROBLEM IS HERE!!!
<DrawerNavigatorItems {...props} />
{/* Plus our custom buttons */}
{userIdExists && (
<View style={NavigationStyles.summary}>
<Ionicons.Button
name="ios-create"
backgroundColor={Colours.moccasin_light}
size={iconMultiplier / 10}
// style={{marginLeft: -20 }}
color="#888"
onPress={() => props.navigation.navigate("CreateWelcome")}
></Ionicons.Button>
<Text
onPress={() => props.navigation.navigate("CreateWelcome")}
style={[
NavigationStyles.exodos,
Platform.OS == "android" ? { marginLeft: -6 } : null
]}
>
Δημιουργία
</Text>
</View>
)}
...
Now it looks like this:
const CustomDrawerContent = props => {
const dispatch = useDispatch();
// This is for showing the Admin screen link, if user is an admin.
const userIdExists = useSelector(state => state.auth.userId);
return (
<View style={{ flex: 1 }}>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
<DrawerContentScrollView {...props}>
{/* These are the default drawer items */}
<DrawerItemList {...props} />
{/* Plus our custom buttons */}
<Drawer.Section>
{userIdExists && (
<View style={NavigationStyles.summary}>
<DrawerItem
label={() => (
<Text
// onPress={() => props.navigation.navigate("CreateWelcome")}
style={[
NavigationStyles.exodos,
Platform.OS == "android" ? { marginLeft: -6 } : null
]}
>
Δημιουργία
</Text>
)}
icon={() => (
<Ionicons.Button
name="ios-create"
backgroundColor={Colours.moccasin_light}
size={iconMultiplier / 10}
// style={{marginLeft: -20 }}
color="#888"
// onPress={() => props.navigation.navigate("CreateWelcome")}
></Ionicons.Button>
)}
onPress={() => props.navigation.navigate("CreateWelcome")}
/>
</View>
)}
// ... more items
</Drawer.Section>
</DrawerContentScrollView>
</SafeAreaView>
</View>
);
};
const MainDrawerNavigator = createDrawerNavigator();
export const MainNavigator = () => {
return (
<MainDrawerNavigator.Navigator
drawerStyle={{
width: width < 900 ? 0.6 * width : 0.4 * width,
backgroundColor: Colours.moccasin_light,
overlayColor: Colours.maroonRGBA
}}
drawerContentOptions={{ activeTintColor: Colours.gr_brown }}
drawerContent={props => <CustomDrawerContent {...props} />}
>
<MainDrawerNavigator.Screen
name="GameNavigator"
component={GameNavigator}
options={{
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
),
drawerIcon: ({ color }) => (
<View style={NavigationStyles.shield}>
<MaterialCommunityIcons
name="shield-cross"
size={iconMultiplier / 8}
color={color}
/>
</View>
)
}}
/>
// ... more screens
</MainDrawerNavigator.Navigator>
);
};
The error I get is:
TypeError: label is not a function. (In 'label({
color: color,
focused: focused
})', 'label' is an instance of Object)
and it's generated at the <DrawerItemList {...props} />
In the docs I read:
import {
DrawerContentScrollView,
DrawerItemList,
} from '#react-navigation/drawer';
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
</DrawerContentScrollView>
);
}
// To add additional items in the drawer, you can use the DrawerItem component:
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem
label="Help"
onPress={() => Linking.openURL('https://mywebsite.com/help')}
/>
</DrawerContentScrollView>
);
}
The first screen that I render is a Navigator, the GameNavigator.
Could that be a problem?
I read in a issue that:
"It's not possible to add navigators inside drawer content. You can achieve custom layouts using a custom router and custom navigator:" source,but I've learned from a course that it is possible! Or does this guy mean something else with drawer content?
The GameNavigator is:
const GameStackNavigator = createStackNavigator();
const GameNavigator = () => {
return (
<GameStackNavigator.Navigator
initialRouteName="Welcome"
screenOptions={defaultNavOptions}
>
<GameStackNavigator.Screen
name="Welcome"
component={WelcomeScreen}
options={WelcomeScreenOptions}
/>
...
</GameStackNavigator.Navigator>
);
};
And the WelcomeScreen is:
const WelcomeScreen = props => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [gridTileAnimValue] = useState(new Animated.Value(0));
const [isConnected, setIsConnected] = useState(false);
// For adding the points that are saved in memory, when connection is established.
useEffect(() => {
const unsub = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
});
return () => unsub();
}, []);
const getPoints = async () => {
let points = await AsyncStorage.getItem("savedPoints");
if (!!points) {
const getEmail = async () => {
const userData = await AsyncStorage.getItem("userData");
if (userData) {
const transformedData = JSON.parse(userData);
const { userEmail } = transformedData;
return userEmail;
}
};
const email = await getEmail();
// Give it some time to get the token and userId,
// because saveData needs them.
setTimeout(
async () => await dispatch(dataActions.saveData(email, +points)),
3000
);
await AsyncStorage.removeItem("savedPoints");
}
};
if (isConnected) getPoints();
useEffect(() => {
getFilters = async () => {
await dispatch(filtersActions.fetchDifficultyLevelFilters());
await dispatch(filtersActions.fetchCategoriesFilters());
};
}, [dispatch]);
useEffect(() => {
props.navigation.setOptions({
headerRight: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="game-info"
iconName={
Platform.OS === "android"
? "md-information-circle-outline"
: "ios-information-circle-outline"
}
// style={{width: width / 8, height: height / 10, paddingTop: height / 35}}
onPress={() => setModalVisible(!modalVisible)}
/>
</HeaderButtons>
)
});
}, [modalVisible, setModalVisible]);
useEffect(() => {
const checkIfInfoNeeded = async () => {
return await AsyncStorage.getItem("NO_infoNeeded");
};
checkIfInfoNeeded().then(NO_infoNeeded => {
if (NO_infoNeeded === "NO") {
return;
} else {
setModalVisible(true);
}
});
}, []);
const animateGridTile = () => {
Animated.timing(gridTileAnimValue, {
toValue: 1,
duration: 1200,
useNativeDriver: false
}).start();
};
useEffect(() => {
animateGridTile();
const unsubscribe = props.navigation.addListener("focus", animateGridTile);
return () => unsubscribe();
}, [animateGridTile]);
useEffect(() => {
const unsubscribe = props.navigation.addListener("willBlur", () =>
gridTileAnimValue.setValue(0)
);
return () => {
unsubscribe();
};
}, []);
const cardStyle = { opacity: gridTileAnimValue };
const renderGridItem = itemData => {
return (
<Animated.View style={cardStyle}>
<CategoryGridTile
color={itemData.item.color}
title={itemData.item.title}
id={itemData.item.id}
onSelect={() => {
if (itemData.item.id == 0) {
props.navigation.navigate({
routeName: "MixedChoicesScreen"
});
} else if (itemData.item.id == 1) {
props.navigation.navigate({
routeName: "MultiChoiceCategories",
params: {
gameType: itemData.item.title
}
});
} else if (itemData.item.id == 2) {
props.navigation.navigate({
routeName: "TrueFalseCategories",
params: {
gameType: itemData.item.title
}
});
}
}}
/>
</Animated.View>
);
};
if (isLoading) {
return (
<CustomLinearGradient>
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colours.moccasin_light} />
</View>
</CustomLinearGradient>
);
}
return (
<CustomLinearGradient>
<View style={styles.flatListContainer}>
{modalVisible && (
<CustomModal
modalVisible={modalVisible}
setModalVisible={setModalVisible}
onRequestClose={() => {
Alert.alert(
"Επιλογές",
"Παρακαλώ επιλέξτε μία από τις δύο επιλογές της καρτούλας: Ναι ή Όχι.",
[{ text: "Εντάξει", style: "default" }]
);
// Alert.alert("Παρακαλώ επιλέξτε μία από τις δύο επιλογές της καρτούλας: Πληροφορίες ή Δεν χρειάζεται.");
}}
textOne="Θέλετε να διαβάσετε τις οδηγίες χρήσεως και τις πληροφορίες σχετικά με τις
δοκιμαστικές εκδόσεις της εφαρμογής."
buttonOneTitle="Ναι"
buttonTwoTitle="Όχι"
onPressOne={async () => {
AsyncStorage.setItem("NO_infoNeeded", "NO");
setModalVisible(false);
props.navigation.navigate("GameInfo");
}}
onPressTwo={async () => {
AsyncStorage.setItem("NO_infoNeeded", "NO");
setModalVisible(false);
}}
/>
)}
<FlatList
// numColumns={2}
keyExtractor={(item, index) => item.id}
data={GAME_TYPES}
renderItem={renderGridItem}
/>
</View>
</CustomLinearGradient>
);
};
export const WelcomeScreenOptions = ({ route, navigation }) => {
return {
title: "ΕΝ ΤΟΥΤΩ ΝΙΚΑ",
headerLeft: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
onPress={() => navigation.toggleDrawer()}
title="Menu"
iconSize={73}
iconName={Platform.OS === "android" ? "md-menu" : "ios-menu"}
// style={{
// width: width / 8,
// height: height / 10,
// paddingTop: height / 35
// }}
/>
</HeaderButtons>
)
};
};
Any suggestion would be appreciated.
Thanks
For drawerLabel you have directly set an object which is wrong
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
),
This should be either a string or a function that returns a component, so you should change it like below
drawerLabel: {()=>(
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
)},
You can refer the docs
https://reactnavigation.org/docs/drawer-navigator/#drawerlabel
I have onboarding and dot component . I want to use opacity animation with interpolate on dots. But it is not working. Do I use onScroll in a wrong way or what ? Thanks to any help
const Onboarding = () => {
const { width } = useOrientation();
const scrollX = new Animated.Value(0);
const onScroll = (event: any) => {
Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }]);
};
return (
<Box flex={1} backgroundColor="mainBackground">
<Box flex={3}>
<Animated.ScrollView onScroll={onScroll} horizontal>
{slides.map((data, index) => (
<Slide key={index} data={data} {...{ index, scrollX }}
/>
))}
</Animated.ScrollView>
</Box>
<Box
flex={1}
>
{slides.map((_, index) => (
<Dot key={index + 1} currentIndex={divide(scrollX, width)} {...{ index }}
/>
))}
</Box>
</Box>
);
};
const Dot = ({currentIndex,index}:DotProps) => {
const opacity = interpolate(currentIndex,{
inputRange:[index-1,index,index+1],
outputRange:[0.5,1,0.5],
extrapolate:Extrapolate.CLAMP,
})
return (
<Animated.View
style={{ opacity, backgroundColor: "#2CB9B0",width: 8,height: 8,borderRadius: 4
}}
/>
);
};