Item Component Collapsed in Flatlist when move to next
Description
I am trying to create a slider using a flatlist; it works fine on most devices, but on the iPhone 12, it misbehaves. The first index item appears correctly, but when I move to the next item, the item container view collapses and all items move to the top of the screen. I have tried both vertical and horizontal scrolling directions. Is there anyone who can assist me in resolving this? I have attached some files for your reference.
https://user-images.githubusercontent.com/50843183/202099491-4f6d6381-fe59-4749-bf87-78abf695f3b7.mp4

Version
0.64.2
Output of npx react-native info
System: OS: macOS 11.6 CPU: (12) x64 Intel(R) Core(TM) i5-10400 CPU @ 2.90GHz Memory: 231.70 MB / 16.00 GB Shell: 5.8 - /bin/zsh Binaries: Node: 19.0.1 - /usr/local/bin/node Yarn: 1.22.19 - /usr/local/bin/yarn npm: 8.19.2 - /usr/local/bin/npm Watchman: 2022.11.07.00 - /usr/local/bin/watchman Managers: CocoaPods: 1.11.2 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.2, iOS 15.2, macOS 12.1, tvOS 15.2, watchOS 8.3 Android SDK: API Levels: 23, 25, 28, 30, 31, 32 Build Tools: 29.0.2, 30.0.0, 30.0.2, 30.0.3, 31.0.0, 32.0.0 System Images: android-30 | Google Play Intel x86 Atom Android NDK: Not Found IDEs: Android Studio: 2021.2 AI-212.5712.43.2112.8815526 Xcode: 13.2.1/13C100 - /usr/bin/xcodebuild Languages: Java: 11.0.15 - /usr/bin/javac npmPackages: @react-native-community/cli: Not Found react: ^17.0.2 => 17.0.2 react-native: ^0.64.2 => 0.64.2 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found
Steps to reproduce
create a flatlist from the example code given.
Snack, code example, screenshot, or link to a repository
import React, {useContext, useEffect, useRef, useState} from 'react'; import { View, SafeAreaView, FlatList, TouchableOpacity, Image, KeyboardAvoidingView, Keyboard, ActivityIndicator, StyleSheet, } from 'react-native'; import {useDispatch, useSelector} from 'react-redux'; import {getFilterIds} from '../../../Actions/FeedActions'; import AppImages from '../../../Theme/AppImages'; import CommonStyle from '../../../Theme/CommonStyle'; import { currentEnv, isIOS, phoneNoVerifyHeaderImages, phoneNoVerifyScreens, STORAGE, width, } from '../../../Utils/Constant'; import { CustomPhoneInput, CustomText, CustomTextInput, OtpInput, PaginatedHeader, } from '../../CommonComponent'; import {CommonActions} from '@react-navigation/native'; import {getErrorMessage, isValidEmail, showToast} from '../../../Utils/Helper'; import {AppContext} from '../../../AppContext'; import { addFCMToken, resendCode, userLogin, userVerify, } from '../../../Services/UserService'; import {updateUserDetail} from '../../../Actions/UserActions'; import {getItemFromStorage, setItemInStorage} from '../../../Utils/Storage'; import {ApiConfig} from '../../../ApiConfig'; import {getOnboardData} from '../../../Actions/OnboardActions'; import DeviceInfo from 'react-native-device-info';
let count = 0;
const VerifyPhone = props => { const { route: {params}, } = props; const {appTheme} = useContext(AppContext); const { flexContainer, bottomContainer, rowCenter, errorInput, verificationText, flexStyle, } = CommonStyle; const {itemContainer, titleText, loader, resendContainer} = styles; const {navigation} = props; const {user} = useSelector(state => state.user); const [listIndex, setListIndex] = useState(params?.index ? params.index : 0); const [error, setError] = useState({}); const [hasError, setHasError] = useState({ email: false, code: false, phone: false, }); const [toggle, setToggle] = useState(false); const [userData, setUserData] = useState({ email: user?.email || '', phone: user?.phone || '', code: '', unmaskedPhone: user?.unmaskedPhone || '', countryCode: user?.countryCode || '', }); const [sendCode, setSendCode] = useState(false); const dispatch = useDispatch(); let flatListRef = useRef('');
useEffect(() => { count = 0; dispatch(getOnboardData()); }, []);
const onTextChange = (key, text, isValid, unmaskedNo, countryCode) => { if (key && text) { userData[key] = text; if (unmaskedNo && countryCode) { userData.unmaskedPhone = unmaskedNo; userData.countryCode = countryCode; } setUserData(userData); if (key === 'email' && listIndex === 0 && !params?.index) { if (!isValidEmail(text)) { setErrorData(key, 'Enter valid email.', true); } else if (isValidEmail(text)) { setErrorData(key, '', false); } }
if (key === 'phone' && listIndex === 1) {
if (!isValid) {
setErrorData(key, 'Enter valid phone number.', true);
} else {
setErrorData(key, '', false);
}
}
if (key === 'code' && listIndex === 2) {
setErrorData(key, '', false);
}
}
};
const setErrorData = (key, msg, value) => { let errorData = error; let hasErrorData = hasError; errorData[key] = msg; hasErrorData[key] = value; setHasError(hasErrorData); setError(errorData); setToggle(!toggle); };
const navigateToRoute = (route, params = {}) => { navigation.dispatch( CommonActions.reset({ index: 0, routes: [{name: route, params: params}], }), ); };
const getComponentFromType = (item, index) => { if (item.type === 'textInput') { return ( <CustomTextInput placeholder={item.placeholder} keyboardType={item.keyboardType} onTextChange={text => { onTextChange(item.key, text); }} value={user[item.key]} autoCapitalize={'none'} /> ); } else if (item.type === 'phoneInput') { return ( <CustomPhoneInput onTextChange={(text, isVerified, unmaskedNo, countryCode) => { onTextChange(item.key, text, isVerified, unmaskedNo, countryCode); }} userData={userData} /> ); } else { return ( <OtpInput digits={6} value={userData.code} onChangeOtp={text => onTextChange(item.key, text)} /> ); } };
const updateStorage = async data => { setItemInStorage('token', data?.token); ApiConfig.token = data.token; ApiConfig.streamKey = data.user.streamAppKey; ApiConfig.streamToken = data.user.streamToken; };
const clearOTP = () => { userData.code = ''; error.code = ''; hasError.code = ''; setUserData(userData); setError(error); setHasError(hasError); };
const registerUser = async () => { try { if (count === 0) { count = count + 1; let registerData = userData; if (user.provider) { registerData.provider = user.provider; registerData.providerId = user.providerId; } registerData.register = true; console.log('registerData', registerData); const response = await userLogin(registerData); if (response.status === 'success') { count = 0; moveToNext(); // ApiConfig.token = response.data.userItem.activationToken; // setItemInStorage('token', response.data.userItem.activationToken); // dispatch(updateUserDetail(response.data.userItem)); // showToast(response.data.code); } } } catch (err) { if (err?.response?.data) { count = 0; setErrorData('phone', getErrorMessage(err), true); } } };
const updateFCMToken = async () => { const localToken = await getItemFromStorage(STORAGE.fcmToken); console.log('localToken', localToken); try { if (localToken) { const deviceId = await DeviceInfo.getDeviceId(); const data = { token: localToken, platform: isIOS ? 'ios' : 'android', environment: currentEnv, deviceId: deviceId, }; console.log('data register firebase', data); const repoonse = await addFCMToken(data); console.log('repoonse', repoonse); } } catch (e) { console.log(e); } };
const verifyUser = async () => { try { if (count === 0) { count = count + 1; let verificationData = userData; verificationData.register = true; console.log('verificationData', verificationData); let response = await userVerify(verificationData); if (response.status === 'success') { console.log('response', response); updateStorage(response.data); ApiConfig.token = response.data.user.activationToken; setItemInStorage('token', response.data.user.activationToken); dispatch( updateUserDetail({ ...userData, ...response?.data?.user, }), ); if (response?.data?.user?.isProfileCompleted) { makeTogetherCall(response?.data?.user); showToast( 'Phone number already exists. Redirecting to together feeds.', ); updateFCMToken(); navigateToRoute('Home'); } else { showToast(response?.message); navigateToRoute('SignUp'); } } } } catch (err) { if (err?.response?.data) { count = 0; userData.code = ''; setUserData(userData); setErrorData('code', getErrorMessage(err), true); } } };
const makeTogetherCall = userObj => { dispatch(getFilterIds({user: userObj})); };
const moveToNext = () => { flatListRef?.scrollToIndex({index: listIndex + 1}); setListIndex(listIndex + 1); };
const scrollToNext = () => { Keyboard.dismiss(); if (listIndex === 1) { registerUser(); } else if (listIndex === 2) { console.log('asdfasdfadsf'); verifyUser(); } else { moveToNext(); } };
const scrollToPrevious = () => { count = 0; Keyboard.dismiss(); if (listIndex !== 0) { if (listIndex === 2) { clearOTP(); } flatListRef?.scrollToIndex({index: listIndex - 1}); setListIndex(listIndex - 1); } else { navigation.goBack(); } };
const checkError = () => { let key = (listIndex !== 0 && ((listIndex === 1 && 'phone') || 'code')) || 'email';
if (key !== 'phone') {
let condition =
key === 'email'
? hasError[key] || !userData[key]
: hasError[key] || !userData[key] || userData[key].length !== 6;
if (condition) {
return true;
}
return false;
} else {
if (hasError[key] || !userData.unmaskedPhone || !userData.countryCode) {
return true;
}
return false;
}
};
const resendOTP = async () => { try { console.log(userData); setSendCode(true); const data = {phone: userData.phone, register: true}; const response = await resendCode(data); if (response.status === 'success') { clearOTP(); setSendCode(false); // showToast(response.data.code); } } catch (error) { setSendCode(false); } };
const renderItem = ({item, index}) => { return ( <View style={itemContainer}> <CustomText xlarge style={titleText}> {item.title} </CustomText> {getComponentFromType(item, index)} {(hasError[item.key] && ( <CustomText large style={errorInput}> {error[item.key]} </CustomText> )) || null} {(item.key === 'code' && ( <TouchableOpacity style={[rowCenter, resendContainer]} onPress={() => resendOTP()}> {(sendCode && ( <ActivityIndicator size={'small'} color={appTheme.theme} style={loader} /> )) || null} <CustomText medium style={verificationText}> {sendCode ? 'Sending Verification Code' : 'Resend Verification Code'} </CustomText> </TouchableOpacity> )) || null} </View> ); };
return ( <SafeAreaView style={flexContainer}> <KeyboardAvoidingView behavior={'padding'} style={flexStyle} keyboardVerticalOffset={isIOS ? '' : -500}> <PaginatedHeader headerImages={phoneNoVerifyHeaderImages} activeIndex={listIndex} onPressBack={() => { scrollToPrevious(); }} /> <View style={flexStyle}> <FlatList showsHorizontalScrollIndicator={false} data={phoneNoVerifyScreens} renderItem={renderItem} keyExtractor={item => item.id} horizontal={true} scrollEnabled={false} ref={ref => { flatListRef = ref; }} initialScrollIndex={params?.index ? params?.index : 0} keyboardShouldPersistTaps={'always'} /> <View style={bottomContainer}> {(!checkError() && ( <TouchableOpacity onPress={() => { scrollToNext(); }}> <Image source={{uri: AppImages.next}} style={CommonStyle.nextImage} resizeMode="contain" /> </TouchableOpacity> )) || ( <Image source={{uri: AppImages.disabled}} style={CommonStyle.nextImage} resizeMode="contain" /> )} </View> </View> </KeyboardAvoidingView> </SafeAreaView> ); };
const styles = StyleSheet.create({ itemContainer: { flex: 1, width: width, paddingHorizontal: 16, marginTop: '10%', }, titleText: { marginBottom: '10%', }, loader: { marginRight: 10, }, resendContainer: { marginTop: 10, }, });
export default VerifyPhone;
@hardikgpatel any updates on this ??
Nop
@hardikgpatel we found the issue its cause of behavior={'padding'} if you would remove that it will work.
check the comment here for more info https://github.com/facebook/react-native/issues/29974#issuecomment-700804626
Okay i will check that
This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.
This issue was closed because it has been stalled for 7 days with no activity.