Description
I have searched open and closed issues for this issue.
- I have searched open and closed issues.
I have read and understood the license (link below).
- I have read and understood the license.
Minimal reproducible example
import { Alert, PermissionsAndroid, Platform, StyleSheet, Text, TouchableOpacity, View, NativeModules } from 'react-native'; import React, { useEffect, useState } from 'react'; import { APP_CUSTOM_FONTS, APP_THEME_COLOR } from '../../themes'; import { useTranslation } from 'react-i18next'; import UpploadBulkFile from './UploadBulkFile'; import { responsiveHeight } from 'react-native-responsive-dimensions'; import * as DocumentPicker from '@react-native-documents/picker'; import SelectedBulkFile from './SelectedBulkFile'; import { openSettings } from 'react-native-permissions'; import GreenTickIcon from '../../assets/images/greenTick.svg'; import RedAlertIcon from '../../assets/images/red_alert_circle.svg'; import SkuNotFoundModal from './SkuNotFoundModal'; import RNFS from 'react-native-fs'; import { BULK_FILE_UPLOAD_CONFIG } from '../../common/constants'; import Loader from '../Loader/loader'; import RNFetchBlob from 'react-native-blob-util'; import { useDispatch } from 'react-redux'; import { setFileUploadedFlag } from '../../../shared/redux/store/store.action'; import useBulkAddToCart from '../../../shared/hooks/cart/useBulkAddToCart'; import userHelper from '../../../shared/helper/user-helper'; import ErrorModal from './ErrorModal'; import { read, utils } from 'xlsx'; import SkuLimitErrorPopup from '../Popup/SkuLimitErrorPopup'; import CommonHelper from '../../../shared/helper/common-helper'; const UpploadBulkOrder = ({ sellers, setOpenSuccessModal }) => { const { t } = useTranslation(); const [fileDetails, setFileDetails] = useState({ fileName: '', fileSize: '', filePath: '' }); const [isSelected, setIsSelected] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [showErrorModal, setShowErrorModal] = useState(false); const [showSkuModel, setShowSkuModal] = useState(false); const [isLoading, setIsLoading] = useState(false); const [someSkuNotAdded, setSomeSkuNotAdded] = useState([]); const [partialAdditions, setPartialAdditions] = useState([]); const [message, setMessage] = useState({ status: '', reason: '' }); const dispatch = useDispatch(); const { addBulkProductsToCart } = useBulkAddToCart(); const cartId = userHelper.getCartID(); const [showSkuLimitErrorModal,setShowSkuLimitErrorModal] = useState(false) const skuLimit = CommonHelper.isRetailerCartLimitEnabled() ? CommonHelper.getRetailerCartLimitValue() : BULK_FILE_UPLOAD_CONFIG.MAX_SKU_VALUES const [totalSKUsUploaded, setTotalSKUsUploaded] = useState(0) useEffect(() => { if (isSelected) { Alert.alert('before dispatch'); dispatch(setFileUploadedFlag(true)); Alert.alert('after dispatch'); } else { dispatch(setFileUploadedFlag(false)); } }, [isSelected]); const requestStoragePermission = async () => { const androidVersion = Platform.OS === 'android' ? parseInt(NativeModules.PlatformConstants.Release, 10) : null; if (Platform.OS === 'android' && androidVersion <= 10) { try { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, { title: 'Permissão de Armazenamento', message: 'Este aplicativo precisa de acesso ao seu armazenamento para selecionar arquivos.', buttonNegative: 'Cancelar', buttonPositive: 'OK' } ); if (granted === PermissionsAndroid.RESULTS.GRANTED) { uploadFileHandler(); } else if (granted === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) { Alert.alert( 'Permissão Necessária', 'A permissão de armazenamento é necessária para enviar arquivos. Por favor, ative-a nas configurações.', [ { text: 'Cancelar', style: 'cancel' }, { text: 'Abrir Configurações', onPress: () => openSettings() } ] ); } else { Alert.alert('Permissão negada.'); } } catch (err) { console.warn(err); } } else { uploadFileHandler(); } }; const validationRules = [ { check: (file) => checkIfFileType(file), isAsyncFn: false }, { check: (file) => checkIfFileSizeExceeded(file), isAsyncFn: false }, { check: (file) => checkIfFileContentIsValid(file), isAsyncFn: true } ]; const checkIfFileSizeExceeded = (file) => { let result = { isError: false, errorMsg: '' }; const maxFileSize = BULK_FILE_UPLOAD_CONFIG.MAX_FILE_SIZE * 1024 * 1024; // 2 MB limit if (file.size > maxFileSize) { result = { isError: true, errorMsg: t('FileSizeLimitErrorMsg') }; } return result; }; const checkIfFileType = (file) => { let result = { isError: false, errorMsg: '' }; if (!(DocumentPicker.types.xlsx === file.nativeType)) { result = { isError: true, errorMsg: t('FileTypeError') }; } return result; }; const checkIfFileContentIsValid = async (file) => { const fileUri = file.uri; try { const fileContent = await RNFS.readFile(fileUri, 'base64'); return new Promise((resolve, reject) => { const workbook = read(fileContent, { type: 'base64' }); const parsedSpreedSheet = parseFile(workbook, resolve) || []; try { if (parsedSpreedSheet?.length === 0) { return resolve({ isError: true, errorMsg: t('FileUploadError') }); } const headerIndex = parsedSpreedSheet?.findIndex((row) => BULK_FILE_UPLOAD_CONFIG.COLUMN_VALUES.every(col => row.includes(col)) ); const spreadSheetContent = parsedSpreedSheet ?.slice(headerIndex + 1) .map((row) => row.map((cell) => (cell === null ? '' : cell))); const contentValidations = [ // { // check: (result) => checkSkuLimitExceeded(result || []), // errorMsg: t('FileSkuLimitExcededError') // }, { check: (result) => checkIfHasMaliciousCode(result || []), errorMsg: t('FileWithMaliciousCodeError') }, { check: (result) => checkColumnNamesModified(headerIndex), errorMsg: t('FileColumnNamesModifidError') }, { check: (result) => checkSkuValuesLengthExceeded(result || []), errorMsg: t('FileSkuLengthExcededError') }, { check: (result) => checkQuantityValuesLengthExceeded(result || []), errorMsg: t('FileQtyLengthExcededError') }, { check: (result) => checkIfSpecialCharactersPresent(result || []), errorMsg: t('FileSpecialCharactersError') } ]; for (const validation of contentValidations) { if (validation.check(spreadSheetContent)) { return resolve({ isError: true, errorMsg: validation.errorMsg }); } } resolve({ isError: false, errorMsg: '' }); } catch (error) { reject(error); } }); } catch (err) { throw new Error('Error while fetching or reading the file'); } }; const parseFile = (workbook, resolve) => { // * check if has more then on tab in the excel file if (workbook?.SheetNames?.length > 1) { return resolve({ isError: true, errorMsg: t('FileTabsExceededLimitError') }); } // * check for malicious code if ( workbook.Workbook?.Names?.some((name) => name.Name.startsWith('VBA')) ) { return resolve({ isError: true, errorMsg: t('FileWithMaliciousCodeError') }); } let spreadsheet = workbook.Sheets[workbook.SheetNames[0]]; for (let cell in spreadsheet) { if (spreadsheet.hasOwnProperty(cell) && cell[0] !== '!') { // * verifies if cell has error -> this is to let embedded images pass if (spreadsheet[cell].t === 'e' && spreadsheet[cell].w === "#VALUE!" && spreadsheet[cell].v === 15) { spreadsheet[cell].w = ''; spreadsheet[cell].v = ''; } } } return utils.sheet_to_json(spreadsheet, { header: 1, blankrows: false }); }; const checkColumnNamesModified = (headerIndex) => { if (headerIndex < 0) { return true; } return false; }; const checkSkuLimitExceeded = (data) => { if (data?.length > skuLimit) { return true; } const distinctSkus = new Set(data.map(row => row[0])) setTotalSKUsUploaded(distinctSkus) return false; }; const checkSkuValuesLengthExceeded = (data) => { const invalidSkuValues = data?.filter((row) => { const firstColumnValue = row[0]; return ( firstColumnValue.toString()?.length > BULK_FILE_UPLOAD_CONFIG.MAX_SKU_LENGTH ); }); if (invalidSkuValues.length > 0) { return true; } return false; }; const checkQuantityValuesLengthExceeded = (data) => { const invalidQuantityValues = data.filter((row) => { const firstColumnValue = row[1]; return ( firstColumnValue.toString()?.length > BULK_FILE_UPLOAD_CONFIG.MAX_QUANTITY_LENGTH ); }); if (invalidQuantityValues.length > 0) { return true; } return false; }; const checkIfSpecialCharactersPresent = (data) => { const specialCharRegex = BULK_FILE_UPLOAD_CONFIG.NOT_ALLOWED_SPECIAL_CHARS; for (let rowIndex = 0; rowIndex < data?.length; rowIndex++) { const row = data?.[rowIndex]; for (const item in row) { const value = row[item].toString(); if (value && specialCharRegex.test(value)) { return true; } } } return false; }; const checkIfHasMaliciousCode = (data) => { const maliciousPatterns = BULK_FILE_UPLOAD_CONFIG.MALICIOUS_PATTERNS; const hasMaliciousContent = data.some((row) => row.some((cell) => { const cellValue = cell ? cell.toString() : ''; return maliciousPatterns.some((pattern) => pattern.test(cellValue)); }) ); const hasUrl = data.some((row) => row.some((cell) => { const cellValue = cell ? cell.toString() : ''; return cellValue.startsWith("http") }) ); return hasMaliciousContent || hasUrl; }; const checkIfFileIsValid = async (response) => { let fileDetails = response[0]; for (const rule of validationRules) { try { let result; if (rule.isAsyncFn) { result = await rule.check(fileDetails); } else { result = rule.check(fileDetails); } if (result?.isError) { return result; } } catch (error) { return { isError: true, errorMsg: t('FileUploadError') }; } } return { isError: false, errorMsg: '' }; }; const uploadFileHandler = async () => { setIsLoading(true); setErrorMessage(''); setIsSelected(false); try { const response = await DocumentPicker.pick({ type: [DocumentPicker.types.xlsx], presentationStyle: 'fullScreen' }); if (response && response.length > 0) { let file = response[0]; if (Platform.OS === 'ios') { const decodedUri = decodeURIComponent(file.uri); file.uri = decodedUri; } const { isError, errorMsg } = await checkIfFileIsValid(response); if (isError) { setErrorMessage(errorMsg); } else { Alert.alert(' completo', Arquivo salvo em: ${file.uri} ${file.name} ${file.size}
); setFileDetails({ fileName: file.name, fileSize: file.size, filePath: file.uri }); setIsSelected(true); } } } catch (err) { Alert.alert(err,'error message catch'); setErrorMessage(t('FileUploadError')); Alert.alert(err,'after error message catch'); } finally { setIsLoading(false); } }; const deleteFileHandler = () => { setIsSelected(false); }; const xlsxToBase64 = async () => { let filePath = fileDetails.filePath; if (Platform.OS === 'ios') { const formattedPath = filePath.replace('file://', ''); filePath = formattedPath; } const base64Data = await RNFetchBlob.fs.readFile(filePath, 'base64'); return data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${base64Data}
; }; const setShowBulkImportStatusModal = ( skusWithError, successMsg, addToCartStatus ) => { const someItensWereAdded = successMsg?.length > 0; const notFound = skusWithError?.filter((sku) => BULK_FILE_UPLOAD_CONFIG.ERROR_NOT_FOUND.includes(sku?.error_message) ); const noStock = skusWithError?.filter((sku) => BULK_FILE_UPLOAD_CONFIG.ERROR_NO_STOCK.includes(sku?.error_message) ); const hasPartialAdditions = successMsg?.some((msg) => msg.includes('Partial qty has been added') ); const hasNotFound = notFound?.length > 0; const hasNoStock = noStock?.length > 0; const partialAdditionsData = successMsg ?.filter((msg) => msg.includes('Partial qty has been added')) ?.map((msg) => { const match = msg.match(/sku (\d+\w*)/); const matchQuantity = msg.match(/,\s*(\d+)/); return match ? { sku: match[1], error_message: t('PartialStock'), requested_qty: matchQuantity[1] } : null; }) ?.filter(Boolean); setPartialAdditions(partialAdditionsData); const reason = !!hasNotFound && !hasNoStock ? BULK_FILE_UPLOAD_CONFIG.SKU_NOT_FOUND : !hasNotFound && !!hasNoStock ? BULK_FILE_UPLOAD_CONFIG.SKU_NO_STOCK : BULK_FILE_UPLOAD_CONFIG.SKU_NO_STOCK_OR_NOT_FOUND; if (!someItensWereAdded) { setMessage({ status: BULK_FILE_UPLOAD_CONFIG.NO_SKU_ADDED_TO_CART, reason: reason }); } else if (hasPartialAdditions) { setMessage({ status: BULK_FILE_UPLOAD_CONFIG.SOME_SKU_PARTIAL_STOCK, reason: BULK_FILE_UPLOAD_CONFIG.SOME_SKU_PARTIAL_STOCK }); } else { setMessage({ status: BULK_FILE_UPLOAD_CONFIG.SOME_SKU_ADDED_TO_CART, reason: reason }); } setSomeSkuNotAdded(skusWithError); setShowSkuModal(addToCartStatus === false); }; const addProductsToCart = async () => { // const isLimitReached = CommonHelper.isRetailerCartLimitReached(totalSKUsUploaded); // if(isLimitReached) { // setShowSkuLimitErrorModal(true); // return // } setIsLoading(true); const sellerVariables = sellers.map( ({ product_principal, ...rest }) => rest ); try { const base64 = await xlsxToBase64(); const { data, errors } = await addBulkProductsToCart({ cartId, base64_encoded_file: base64, seller_details: sellerVariables }); if (data?.bulkAddToCart?.status === true) { const hasPartialQtyMessage = data?.bulkAddToCart?.success_msgs?.length > 0 && data.bulkAddToCart.success_msgs.some((msg) => msg.includes('Partial qty has been added for sku') ); if (hasPartialQtyMessage) { setMessage({ status: BULK_FILE_UPLOAD_CONFIG.SOME_SKU_PARTIAL_STOCK, reason: BULK_FILE_UPLOAD_CONFIG.SOME_SKU_PARTIAL_STOCK }); setShowBulkImportStatusModal( data?.bulkAddToCart?.error_items, data?.bulkAddToCart?.success_msgs, data?.bulkAddToCart?.status ); setShowSkuModal(true); } else { setOpenSuccessModal(true); } setIsSelected(false); } else if (errors) { if(errors[0].message ==='limit_exceeded'){ setShowSkuLimitErrorModal(true) }else{ setShowErrorModal(true); } } else { setShowBulkImportStatusModal( data?.bulkAddToCart?.error_items, data?.bulkAddToCart?.success_msgs, data?.bulkAddToCart?.status ); } deleteFileHandler(); } catch (error) { console.error('Error adding products to cart:', error); } setIsLoading(false); }; const skuLimitErrorModal = () => { setShowSkuLimitErrorModal(true); } return ( {isLoading && } {t('Import-Order')} {t('UploadTheFileToSeePromos')} {isSelected && ( )} {isSelected && ( {t('FileUploadedSuccesMsg')} )} {typeof errorMessage === 'string' && errorMessage !== '' && ( {errorMessage} )} <TouchableOpacity testID="addProductsToCart" style={[ styles.btn, { backgroundColor: isSelected ? APP_THEME_COLOR.cart_button_blue : APP_THEME_COLOR.filter_background_gray } ]} onPress={addProductsToCart} disabled={!isSelected} > <Text style={[ styles.btnTxt, { color: isSelected ? APP_THEME_COLOR.white : APP_THEME_COLOR.spanish_gray } ]} > {t('BulKOrderAddTOCart')} {showSkuModel && ( )} {showErrorModal && ( )} {showSkuLimitErrorModal && ( <SkuLimitErrorPopup isVisible={showSkuLimitErrorModal} onClose={() => setShowSkuLimitErrorModal(false)} headerTxt={t('tooManyProdInCart')} subText={t('tooManyProdInCartSubText', { limit: CommonHelper.getRetailerCartLimitValue() })} /> )} ); }; export default UpploadBulkOrder; const styles = StyleSheet.create({ container: { display: 'flex', padding: 15, gap: 20, borderWidth: 1, borderRadius: 8, borderColor: APP_THEME_COLOR.cart_disable_grey }, headingContainer: { gap: 10, paddingBottom: 10, borderBottomWidth: 1, borderColor: APP_THEME_COLOR.cart_disable_grey }, headingTxt: { color: APP_THEME_COLOR.instruction_hint, fontSize: APP_CUSTOM_FONTS.fontSize18.fontSize, fontFamily: APP_CUSTOM_FONTS.usFontFamily.fontFamilyBold }, headingSubTxt: { color: APP_THEME_COLOR.input_field_hint_color, fontSize: APP_CUSTOM_FONTS.fontSize14.fontSize, fontFamily: APP_CUSTOM_FONTS.usFontFamily.fontFamily }, btn: { display: 'flex', justifyContent: 'center', alignItems: 'center', paddingVertical: responsiveHeight(1.8), borderRadius: 8 }, btnTxt: { fontSize: APP_CUSTOM_FONTS.fontSize16.fontSize, fontFamily: APP_CUSTOM_FONTS.usFontFamily.fontFamilyBold }, succesMsg: { color: APP_THEME_COLOR.RecoveryPasswordValidClr, fontSize: APP_CUSTOM_FONTS.fontSize12.fontSize, fontFamily: APP_CUSTOM_FONTS.usFontFamily.fontFamilyMedium, flex: 1 }, msgContainer: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 10 }, errorMsg: { color: APP_THEME_COLOR.RecoveryErrorMsg, fontSize: APP_CUSTOM_FONTS.fontSize11.fontSize, fontFamily: APP_CUSTOM_FONTS.usFontFamily.fontFamilyMedium, lineHeight: 16, flex: 1 }, warningMsg: { color: APP_THEME_COLOR.minOrderNotMetMsg, fontSize: APP_CUSTOM_FONTS.fontSize12.fontSize, fontFamily: APP_CUSTOM_FONTS.usFontFamily.fontFamilyMedium, flex: 1 }, linkTxt: { color: APP_THEME_COLOR.cart_button_blue, fontSize: APP_CUSTOM_FONTS.fontSize16.fontSize, fontFamily: APP_CUSTOM_FONTS.usFontFamily.fontFamilyMedium, textAlign: 'center', textDecorationLine: 'underline' } });
What platform(s) does this occur on?
iOS
Did you reproduce on a real device or emulator / simulator?
real device
Steps to reproduce
We are using @react-native-documents/picker for picking the file
so the issue is reproducible only in ios builds in physical device(iphone 14(ios 18.5) and other iphones as well) while searching the file and uploading(normal file upload is working fine without search), whereas if we run the app locally in real device its working fine(with search and without search)
this is how we are picking the file and validating and all the validations are working fine , soon after the validation the app is getting crashed -
const uploadFileHandler = async () => {
setIsLoading(true);
setErrorMessage('');
setIsSelected(false);
try {
const response = await DocumentPicker.pick({
type: [DocumentPicker.types.xlsx],
presentationStyle: 'fullScreen'
});
if (response && response.length > 0) {
let file = response[0];
if (Platform.OS === 'ios') {
const decodedUri = decodeURIComponent(file.uri);
file.uri = decodedUri;
}
const { isError, errorMsg } = await checkIfFileIsValid(response);
if (isError) {
setErrorMessage(errorMsg);
} else {
Alert.alert(' completo', `Arquivo salvo em: ${file.uri} ${file.name} ${file.size}`);
setFileDetails({
fileName: file.name,
fileSize: file.size,
filePath: file.uri
});
setIsSelected(true);
}
}
} catch (err) {
Alert.alert(err,'error message catch');
setErrorMessage(t('FileUploadError'));
Alert.alert(err,'after error message catch');
} finally {
setIsLoading(false);
}
};
below are the permissions wer have added in info.plist
NSDocumentsFolderUsageDescription
Need access to save your file
LSSupportsOpeningDocumentsInPlace
UIFileSharingEnabled
UISupportsDocumentBrowser
NSFileProviderDomainUsageDescription
App requires access to files to import orders.
NSDocumentPickerSandboxMode
YES
NSDownloadsFolderUsageDescription
This app requires access to the Downloads folder to upload files.
If the issue is related to specific file(s), I have linked the files so that others can reproduce exactly what I see.
- I have attached files necessary to reproduce the problem (if applicable).
Stacktrace of the crash (if applicable)
No error message, only crash
Your computer environment
System:
OS: macOS 15.4
CPU: (14) arm64 Apple M3 Max
Memory: 808.16 MB / 36.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 22.5.1 - /opt/homebrew/bin/node
Yarn: Not Found
npm: 10.8.2 - /opt/homebrew/bin/npm
Watchman: 2024.08.05.00 - /opt/homebrew/bin/watchman
Managers:
CocoaPods: 1.15.2 - /opt/homebrew/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 24.2, iOS 18.2, macOS 15.2, tvOS 18.2, visionOS 2.2, watchOS 11.2
Android SDK: Not Found
IDEs:
Android Studio: 2023.3 AI-233.14808.21.2331.11709847
Xcode: 16.2/16C5032a - /usr/bin/xcodebuild
Languages:
Java: 17.0.11 - /usr/bin/javac
npmPackages:
@react-native-community/cli: Not Found
react: 18.2.0 => 18.2.0
react-native: 0.71.3 => 0.71.3
react-native-macos: Not Found
npmGlobalPackages:
*react-native*: Not Found