You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo React Native中HomeModal组件渲染错误求助

Expo React Native 渲染错误排查方案

问题描述

使用Expo开发React Native应用,原本运行正常,接入expo-image-picker实现相册访问功能后,Android和iOS端均出现Render Error。怀疑是导入问题,但点击导入路径可跳转至正确文件。

相关代码

index.js

import { useState } from 'react';
import { View, Text, ScrollView, SafeAreaView, Image} from 'react-native';
import { Stack, useRouter } from 'expo-router';

import { COLORS, icons, SIZES } from '../constants';
import { ScreenFooterBtn, Welcome } from '../components';
import HomeModal from '../components/home/welcome/HomeModal';

const Home = () =>{
    const router = useRouter();
    const [isModalVisible, setModalVisible] = useState(false);

    const openModal = () => setModalVisible(true);
    const closeModal = () => setModalVisible(false);
    
    return (
        <SafeAreaView style={{ flex: 1, backgroundColor: COLORS.gray }}>
            <Stack.Screen
            options={{
                headerStyle: { backgroundColor: COLORS.gray },
                headerShadowVisible: false,
                headerTitleAlign: 'center',
                headerTitle: () => (
                    <Image 
                        source={icons.logo}
                        style={{width:50, height:50}}
                    />
                )
            }}
            />

            <ScrollView showsVerticalScrollIndicator={false}>
                <View
                    style={{
                        flex: 1,
                        padding: SIZES.medium
                    }}>

                    <Welcome/>

                </View>
            </ScrollView>
            
            <View style={{flexDirection: 'row', justifyContent: 'space-around', padding: SIZES.medium}}>
                <ScreenFooterBtn iconUrl={icons.home}  dimensions="115%" />
                <ScreenFooterBtn iconUrl={icons.create} dimensions="100%" handlePress={openModal} />
                <ScreenFooterBtn iconUrl={icons.profile} dimensions="80%" />
            </View>
            
            <HomeModal visible={isModalVisible} closeModal={closeModal} />
        </SafeAreaView>
        
    )
}

export default Home;

HomeModal.jsx

import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, TextInput, Alert, Platform } from 'react-native';
import { Modal } from 'react-native-modal';
import * as ImagePicker from 'expo-image-picker';
import styles from './HomeModal.style';
import { COLORS, SIZES, icons } from '../../../constants';
import { ModalBtn } from '../../index';

const HomeModal = ({ visible, closeModal }) => {
  const [textInput, setTextInput] = useState('');
  const trimmedText = textInput.trim();

  const handleTextChange = (text) => {
    if (text.length <= 500) {
      setTextInput(text);
    }
  };

  const handleSubmit = () => {
    closeModal();
    setTextInput('');
  };

  const handleClose = () => {
    if (trimmedText.length > 0) {
      Alert.alert(
        'Discard Changes',
        'Are you sure you want to discard your changes?',
        [
          {
            text: 'Cancel',
            style: 'cancel',
          },
          {
            text: 'Discard',
            style: 'destructive',
            onPress: () => {
              closeModal();
              setTextInput('');
            },
          },
        ],
        { cancelable: true }
      );
    } else {
      closeModal();
      setTextInput('');
    }
  };

  const openImagePicker = async () => {
    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
  
    if (status !== 'granted') {
      Alert.alert('Permission Denied', 'Please grant gallery permission to proceed.');
      return;
    }
  
    const result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: true,
      quality: 1,
    });
  
    if (!result.canceled) {
      console.log('Image URI:', result.uri);
      // Handle the selected image, e.g., upload it or display it
    }
  };
  

  const handleImageBtnPress = async () => {
    openImagePicker();
  };

  useEffect(() => {
    // You can use useEffect to request permissions when the component mounts
    (async () => {
      if (Platform.OS === 'ios') {
        const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
        if (status !== 'granted') {
          Alert.alert('Permission Denied', 'Please grant gallery permission to proceed.');
        }
      }
    })();
  }, []);

  return (
    <Modal
      isVisible={visible}
      onSwipeComplete={closeModal}
      swipeDirection={['down']}
      style={styles.modalContainer}
    >
      <View style={styles.modalContent}>
        {/* Top part of the modal */}
        <View style={styles.topSection}>
          {/* Close button in the upper-left corner */}
          <TouchableOpacity style={styles.closeButton} onPress={handleClose}>
            <Text style={styles.closeButtonText}>Cancel</Text>
          </TouchableOpacity>
          <View style={styles.modalTitleContainer}>
            <Text style={styles.modalTitle}>New post</Text>
          </View>
          {/* Button to submit the text input*/}
          <TouchableOpacity
            style={[
              styles.submitButton,
              trimmedText.length > 0 && styles.submitButtonWithText,
            ]}
            onPress={trimmedText.length > 0 ? handleSubmit : null}
            disabled={trimmedText.length <= 0}
          >
            <Text style={[styles.submitButtonText, { color: COLORS.lightWhite }]}>Submit</Text>
          </TouchableOpacity>
        </View>

        {/* Content of the modal */}
        <View style={styles.modalInnerContent}>
          {/* TextInput for user input */}
          <TextInput
            style={styles.input}
            multiline
            placeholder='What happened?'
            value={textInput}
            onChangeText={handleTextChange}
            autoFocus={true}
          />
        </View>

        {/* Gallery button and tag button */}
        <View style={{ flexDirection: 'row-reverse', justifyContent: 'left', marginTop: -10 }}>
          <TouchableOpacity onPress={handleImageBtnPress}>
            <ModalBtn iconUrl={icons.image} dimensions="80%" />
          </TouchableOpacity>

          <ModalBtn iconUrl={icons.tag} dimensions="60%" />
        </View>
      </View>
    </Modal>
  );
};

export default HomeModal;

排查与修复步骤

1. 确保ImagePicker版本兼容

执行expo install expo-image-picker,而非直接用npm/yarn安装,该命令会自动匹配当前Expo SDK对应的兼容版本,避免版本不匹配导致的渲染异常。

2. 补充iOS权限配置

iOS要求必须在app.json中添加相册权限说明,否则会触发错误。在app.json的ios字段中添加:

"ios": {
  "infoPlist": {
    "NSPhotoLibraryUsageDescription": "需要访问您的相册以选择图片"
  }
}

3. 修复ImagePicker返回值处理

新版Expo ImagePicker的launchImageLibraryAsync返回结果中,图片URI位于assets数组内,原代码中result.uri会导致未定义错误,修改为:

if (!result.canceled) {
  console.log('Image URI:', result.assets[0].uri);
}

4. 检查react-native-modal依赖

确认已安装react-native-modal,执行:

npm install react-native-modal
# 或
yarn add react-native-modal

5. 验证组件导入路径

检查ModalBtn组件的导入import { ModalBtn } from '../../index';,确保../../index.js(或../../index.ts)中正确导出了ModalBtn组件。

6. 清理缓存重启项目

执行expo r -c清除Expo缓存,然后重新启动开发服务器,解决缓存导致的异常。

内容的提问来源于stack exchange,提问作者eviirus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 19:20:55