Search code examples
javascriptreactjsreact-nativeuse-effect

react native jsx is being rendered before state is updated and throwing an undefined error


I have a react native project I am working on. There are two screens. One screen is a list of properties and details that render based on an api call to zillows api. When a property is clicked on, I want it to go to a different screen with the zpid that is passed to the new screen.

On the new screen, I have two states that are set. I have a loading state that is set to true. loading will be set to true until the new api call is made and until the loading state is set to false, I want to screen to render loading text. Once the loading is set to false and the property state is set to the results of the api call, I want to render the property details on the screen.

My issue right now is that I have a useEffect right now that runs the zillow api call right when the screen loads. Once the results are stored in the property state, I set the loading to false.

When this happens I am getting an undefined variable issue that is suppsed to render the property price from the property state. I know that the issue is caused when it is trying to grab from the property state before it is loaded but I am not sure how to delay rendering the content until after the property state is set.

component:

import React, { useState, useEffect } from 'react'
import { Dimensions } from 'react-native'
import { View, Image, Text, StyleSheet, FlatList, TextInput, Button } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'

import { propertyOptions } from '../api/zillowApi'

import { convertToDollars } from '../utilities'

import axios from 'axios';

const PropertyDetail = (props) => {
  const {
    route
  } = props

  const [property, setProperty] = useState()
  const [loading, setLoading] = useState(true)

  let zpid = route.params.zpid

  let deviceWidth = Dimensions.get('window').width
  var aspectHeight = (deviceWidth / 1.78) + 1
  let carouselImageWidth = (deviceWidth / 4)
  let carouselImageHeight = (carouselImageWidth / 1.78) 

  let options = propertyOptions
  options.params.zpid = zpid
 
  useEffect(() => {
    axios.request(options)
      .then(function (response) {
        // console.log("single property: ", response.data);
        setProperty(response)
        setLoading(false)
      }).catch(function (error) {
        console.error(error);
      });
  }, [])

  const loadingContent = <Text>Loading</Text>

  const loadedContent = <ScrollView>
    <View style={styles.imagesContainer} className="images-container">
      <View style={[styles.mainImageContainer,{height: aspectHeight}]} className="main-image-container">
        <Image style={styles.mainImage} source={{uri: property.data.imgSrc}}/>
      </View>
      <View style={styles.carouselContainer} className="image-carousel-image">
        <FlatList 
          horizontal
          data={carouselImages}
          renderItem={({item}) => <Image style={{height: carouselImageHeight, width:carouselImageWidth}} source={require('../../assets/luxury-home-1.jpeg')}/>}
        />
      </View>
    </View>
    
    ...


        <View style={styles.taxHistoryContainer}>
      <View style={styles.contactContainer}>
        <Text>Listing Agent: Omar Jandali (DRE# 02151051)</Text>
        <Text>Listing Brokerage: Compass (DRE# 00132433)</Text>
      </View>
      <View style={styles.expense}>
        <TextInput placeholder='First Name'/>
        <TextInput placeholder='Last Name'/>
      </View>
      <View style={styles.expense}>
        <TextInput placeholder='Subject'/>
      </View>
      <View style={styles.expense}>
        <TextInput placeholder='Message'/>
      </View>
      <Button title='Submit'/>
      
    </View>
    
  </ScrollView>

  return(
    <View>
      {
        loading == true ? loadingContent : loadedContent
      }
    </View>
  )
}


I want to delay rendering the content until I know that the property state is set and I dont know how to do that...


Solution

  • You can do an early return, below the useEffect and your components.

    (sorry if the component names are different).

    if (!property) return <LoadingContent />
    
    return (
      <View>
       <LoadedContent />
      </View>
     )
    

    Also, you should set your loading to false in the useState. Then when making your axios call, set it to true.

    const [loading, setLoading] = useState(false);
    
    useEffect(() => {
        setLoading(true);
    
      try {
        const res = await axios.request(options);
      
        if (res) {
         setProperty(res.data);
        }
      } catch ...
       finally {
        setLoading(false);
      }
      
    }, [])
    

    on another note, is there a reason why the initial value of your property state value is nothing? I normally would set it to null, so it's definite that it's false if there is no update to that state.