Search code examples
javascriptreact-nativeapiaxiosreact-flatlist

React Native - "Undefined is not an object" error when render a screen that take data from API


I am a newbie on React Native. I am making an application that takes data from API and displays it on the screen. When this screen is rendered gives the following error: Render Error undefined is not an object (evaluating 'character.origin.name')

Here codes at screen:

const CharacterDetailScreen = (props) => {

    const { styles, colors } = useThemedValues(getStyles);
    const loc = useLocalization();

    **// state for character detail**
    const [character, setCharacter] = useState([])

    **// this id come from previous screen**
    const { characterId } = props.route.params

    useEffect(()=>{
        Axios.get('character/'+characterId)
            .then(response =>{
                characterDetail = response.data
                setCharacter(characterDetail)
                console.log(characterDetail.name) // if characterId=89, writes "dale" in console
            })
            .catch(error => {
                console.log(error)
            })
    },[])
 
    return (
        <View style={styles.container}>
            <View style={styles.imageContainer}>
                <Image source={{ uri: character.image }}
                    style={styles.image}
                />
            </View>
            <View style={styles.characterNameContainer}>
                <Text style={styles.characterName}>{character.name}</Text>
            </View>
            <View style={styles.detailContainer}>
                <Text style={styles.detailText} >{loc.t(texts.status)}{character.status}</Text>
                <Text style={styles.detailText} >{loc.t(texts.species)}{character.species}</Text>
                <Text style={styles.detailText} >{loc.t(texts.gender)}{character.gender}</Text>
                <Text style={styles.detailText} >{loc.t(texts.type)}{character.type}</Text>
                <Text style={styles.detailText} >{loc.t(texts.origin)}{character.origin.name}</Text>
                <Text style={styles.detailText} >{loc.t(texts.location)}{character.location.name}</Text>
            </View>
        </View>
    );
};

export default CharacterDetailScreen;

I checked the incoming data from API. Is the error due to the fact that the data has not yet come from the api when the page is rendered?

Here incoming data example from API:

const character = {
    "created": "2017-12-01T10:32:08.340Z", 
    "episode": ["https://rickandmortyapi.com/api/episode/5"], 
    "gender": "Male", 
    "id": 89, 
    "image": "https://rickandmortyapi.com/api/character/avatar/89.jpeg", 
    "location": {"name": "Giant's Town", "url": "https://rickandmortyapi.com/api/location/14"}, 
    "name": "Dale", 
    "origin": {"name": "Giant's Town", "url": "https://rickandmortyapi.com/api/location/14"}, 
    "species": "Mythological Creature", 
    "status": "Dead", 
    "type": "Giant", 
    "url": "https://rickandmortyapi.com/api/character/89"
}


Solution

  • The simple way to fix this is to pre define your data:

    const [character, setCharacter] = useState({
        "created": "", 
        "episode": [], 
        "gender": "", 
        "id": 0, 
        "image": undefined, 
        "location": {"name": "", "url": ""}, 
        "name": "", 
        "origin": {"name": "", "url": ""}, 
        "species": "", 
        "status": "", 
        "type": "", 
        "url": ""
    })
    

    That way you never have to second guess whether a property on character is defined and you dont go through the head ache of adding a null check for each parameter on character you might use.

    Another way to address this is to default character to null and render the ui conditionally

    const CharacterDetailScreen = (props) => {
    
        const { styles, colors } = useThemedValues(getStyles);
        const loc = useLocalization();
    
        **// state for character detail**
        const [character, setCharacter] = useState(null])
    
        **// this id come from previous screen**
        const { characterId } = props.route.params
    
        useEffect(()=>{
            Axios.get('character/'+characterId)
                .then(response =>{
                    characterDetail = response.data
                    setCharacter(characterDetail)
                    console.log(characterDetail.name) // if characterId=89, writes "dale" in console
                })
                .catch(error => {
                    console.log(error)
                })
        },[])
     
        return (
            <View style={styles.container}>
                {character ? (
                    <View style={styles.imageContainer}>
                        <Image source={{ uri: character.image }} style={styles.image}/>
                    </View>
                    <View style={styles.characterNameContainer}>
                        <Text style={styles.characterName}>{character.name}</Text>
                    </View>
                    <View style={styles.detailContainer}>
                        <Text style={styles.detailText} >{loc.t(texts.status)}{character.status}</Text>
                        <Text style={styles.detailText} >{loc.t(texts.species)}{character.species}</Text>
                        <Text style={styles.detailText} >{loc.t(texts.gender)}{character.gender}</Text>
                        <Text style={styles.detailText} >{loc.t(texts.type)}{character.type}</Text>
                        <Text style={styles.detailText} >{loc.t(texts.origin)}{character.origin.name}</Text>
                        <Text style={styles.detailText} >{loc.t(texts.location)}{character.location.name}</Text>
                    </View>
                ) : (
                    <Loading/>
                )}
            </View>
        );
    };
    
    export default CharacterDetailScreen;