Home > Software engineering >  react native jsx is being rendered before state is updated and throwing an undefined error
react native jsx is being rendered before state is updated and throwing an undefined error

Time:07-13

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...

CodePudding user response:

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.

  • Related