react-nativeimageexpoasset-management

React Native Expo App: Local Images Not Displaying Despite Correct Paths and Styles


I'm encountering an issue in my React Native Expo app where local images stored in the assets directory are not displaying, despite having the correct paths and styles applied. I've tried using different images in PNG format, verified the direct URLs of the photos stored in assets, and ensured that every path is accurate. However, only non-local images with external links (such as images stored on the web) are being displayed.

I've attempted various troubleshooting steps to resolve this issue:

Checked the paths of the images in the assets directory to ensure they are correct.
Verified the image formats (PNG) and attempted using different images to rule out any specific image-related issues.
Confirmed that the styles applied to the image components are correct and that the container view shows the space allocated for the image.
Verified that the images are properly imported and referenced in the source attribute of the Image component.

Despite these efforts, the local images still fail to display. I expected the images to be rendered correctly within the app, as they do when using non-local images with external links.

Can anyone help me troubleshoot this issue and suggest potential solutions? Any insights or suggestions would be greatly appreciated. Thank you!

My config:

react-native-cli: 2.0.1 react-native: 0.72.12 expo SDK version: 49.0.0

Here is an example of my file with image i want to display:

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, TextInput, KeyboardAvoidingView, SafeAreaView, Image } from 'react-native'
import { useDispatch } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import { login } from '../store/reducers/user'
import AuthAPI from '../services/authAPI'
import FontAwesome from 'react-native-vector-icons/FontAwesome'
import AsyncStorage from '@react-native-async-storage/async-storage';

// Custom input component
const CustomInput = ({ icon, placeholder, onChangeText, value, secureTextEntry, error, onFocus, onBlur }) => {
  return (
    <View style={styles.inputContainer}>
      <FontAwesome name={icon} style={styles.icon} />
      <TextInput
        placeholder={placeholder}
        onChangeText={onChangeText}
        value={value}
        secureTextEntry={secureTextEntry}
        style={styles.input}
        onFocus={onFocus}
        onBlur={onBlur}
      />
      {error && <Text style={styles.error}>{error}</Text>}
    </View>
  );
};

function SigninScreen({ setIsLoggedIn}) {
  const dispatch = useDispatch(); 

  const [signinEmail, setSigninEmail] = useState("");
  const [signinPassword, setSigninPassword] = useState("");
  const [errors, setErrors] = useState({}); // State to store errors
  const navigation = useNavigation();

  const handleInputFocus = () => {
    // Clear errors when input is focused
    setErrors({});
  };

  const handleSignin = async () => {
    // Validation logic
    const errors = {};
    if (!signinEmail.trim()) {
      errors.email = "L'email est requis";
    }
    if (!signinPassword.trim()) {
      errors.password = "Le mot de passe est requis";
    }

    // Si des erreurs sont détectées, arrêtez le processus de connexion
    if (Object.keys(errors).length > 0) {
      setErrors(errors);
      return;
    }

    try {
      // Data in json
      const data = await AuthAPI.signin({ password: signinPassword, email: signinEmail });

      if (data.result) {
        dispatch(login({
          token: data.token,
          email: signinEmail
        }))
        AsyncStorage.setItem('token', data.token)
        AsyncStorage.setItem('isLoggedIn', JSON.stringify(true));
        setIsLoggedIn(true);
        setSigninEmail("");
        setSigninPassword("");
        
      } else {
        alert("Mauvais email ou mot de passe");
      }
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <SafeAreaView>
      <KeyboardAvoidingView style={styles.main}>
        <Text style={styles.title}>Sign In</Text>
        <View style={styles.logoContainer}>
          <Image style={styles.logo} source={require('../assets/logo_em.png')} />
        </View>
        <View style={styles.form}> 
          <CustomInput
            icon="user-o"
            placeholder="Email"
            onChangeText={(value) => setSigninEmail(value)}
            value={signinEmail}
            error={errors.email} // Pass error message to custom input
            onFocus={handleInputFocus} // Clear errors when input is focused
          />
          <CustomInput
            icon="lock"
            placeholder="Password"
            secureTextEntry={true}
            onChangeText={(value) => setSigninPassword(value)}
            value={signinPassword}
            error={errors.password}
            onFocus={handleInputFocus} // Clear errors when input is focused
          />
          <TouchableOpacity style={{ marginBottom: 6, marginTop: -5 }}>
            <Text style={{ fontWeight: 'bold', marginLeft: '40%' }}>Forgot your password ?</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.button} onPress={handleSignin}>
            <Text style={styles.buttonText}>Sign In</Text>
          </TouchableOpacity>
          <Text style={{ fontWeight: 'bold', marginBottom: 5 }}>Don't have an account yet ? </Text>
          <TouchableOpacity style={styles.button2} onPress={() => navigation.navigate("Signup")}>
            <Text style={styles.buttonText2}>Sign Up</Text>
          </TouchableOpacity>
        </View>

      </KeyboardAvoidingView>

    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  main: {
    width: '100%',
    height: '100%',
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  title: {
    color: 'black',
    textAlign: 'center',
    fontSize: 30,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  logoContainer: {
    flex:1
  },
  logo: {
    width: 50,
    height: 50,
  },
  form: {
    width: '80%',
    alignItems: 'center'
  },
  inputContainer: {
    position: 'relative',
    marginBottom: 10,
    width: '100%',
  },
  icon: {
    position: 'absolute',
    fontSize: 20,
    color: 'black',
    zIndex: 1,
    left: 10,
    top: 13,
  },
  input: {
    backgroundColor: "white",
    borderWidth: 1,
    borderColor: "black",
    borderRadius: 10,
    padding: 10,
    width: '100%',
    paddingLeft: 40, // Adjusted for icon width and position
  },
  button: {
    backgroundColor: 'black',
    borderRadius: 10,
    padding: 10,
    marginBottom: 10,
    width: '100%',
    alignItems: 'center',
    marginTop: '5%'
  },
  buttonText: {
    color: 'white',
    fontWeight: 'bold',
  },
  button2: {
    borderRadius: 10,
    padding: 10,
    marginBottom: 10,
    borderWidth: 1,
    borderColor: 'black',
    width: '100%',
    alignItems: 'center',
  },
  buttonText2: {
    color: 'black',
  },
  error: {
    color: 'red',
    marginTop: 5,
    textAlign: 'center', // Center the error message
  },
});

export default SigninScreen;

Here is a snapshot of my project structure:

project snapshot


Solution

  • I usually manage static assets with a separately maintained assets directory so I do not have to create an absolute path for each Image component. This also allows you to use images dynamically in expo.

    For example, create an images.js file in your assets folder. Then create single paths for all of your images. This code would look like this for your project:

    //images.js
    
    const icon = require('./icon.png')
    const logo = require('./logo_em.png')
    
    export default{
    icon,
    logo
    };
    

    Then simply import this file in the rest of your app where you need to use the Image component. This would look like this:

    //SigninScreen.js
    
    import images from "../assets/images";
    
    ...
    
    <Image style={styles.logo} source={images.logo} />
    
    

    This helps to organize your code and keep your directories clean. It also loads all of your resources in one place, allowing them to be explicitly defined during app bundling.

    You can try to do this, but I am not sure that is where your issue is coming from. There have been past issues with sdk 49 and 50 creating breaking changes in uploading local files. Sometimes there are deprecated dependencies that create this issue. Check your dependencies and consider updating. You can find more information about that here.

    I recently had to do this and was stuck in dependency hell for an hour or so. I found that using "react-native": "0.73.4" and "expo": "^50.0.7" and "eas-cli": "^0.49.0" together have worked for me.

    Hope this helps!