reactjstypescriptionic-frameworkionic-react

Conditional Rendering not waiting for Fetch to Complete


In the App i fetch Data from Firebase/Firestore, parse it to a Interface and add the object to the State Array. After that i want to render it. But react wont render the array. I Have commented the Code that you can see what i thought it should do.

const Tab1: React.FC = () => {

  let spiceArray: singleSpice[] = [];

  interface singleSpice {
    spice: string,
    grams: number,
  }

 

  useEffect(() => {
    //fetch the data --> Works well
    async function loadData() {
      const data = await getCollections();
    // Add the Data to the Spicearray --> Works  
      data.forEach((e: any) => spiceArray.push(e));
    }
    // Trigger Function onComponentMount
    loadData();
    // This function will never log "Worked" even tho the loadData is Async
    if(spiceArray.length > 0){
      console.log("Worked")
    }
    
  })



  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonTitle>Tab 1</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent fullscreen>
        <IonHeader collapse="condense">
          <IonToolbar>
            <IonTitle size="large">Tab 1</IonTitle>
          </IonToolbar>
        </IonHeader>
        //Transfer the spiceArray to the next Child as Props --> Works
        <CustomTab1 spices={spiceArray}/>
        <ExploreContainer name="Tab 1 page" />
        <IonButton onClick={() => console.log(spiceArray.length)}> Test me</IonButton>
        
      </IonContent>
    </IonPage>
  );
};

export default Tab1;

Child

    //Interface for Props
    interface Props {
        spices: Spice[];
    }
    //Interface to get access to inner Attributes of the SpiceObjectArray
    interface Spice {
        spice: string;
        grams: number;
    }
    
    const CustomTab1: React.FC<Props> = ({spices}: Props) => {
        return (
        <IonList>
            //Conditional Render 
            {spices.length > 0 && spices.map(entry => <IonItem id={entry.spice}>{entry.spice}</IonItem>)}
            <IonButton onClick={() => console.log(spices)}>Test</IonButton>
        </IonList>
    
        );
      };
    
      export default CustomTab1;

Crazy Thing is. Right now when i F5 the IonicApp none of the spices is Displayed. But when i change the text inside the IoNButton on CustomTab1-Component and save the simple change the spices getting rendered. But if i press F5 to refresh the app everything is gone again.How can i make the app wait until the API delivers the data?


Solution

  • React doesn't rerender when variables change. You say you are passing the fetched object to the "state array" but spiceArray is not a piece of react state. In a functional component, state is declared with useState.

    When your page initially loads, there is no data, and you have the right idea to check if there is first any data before trying to map over/render it. But in order to get the component to rerender and thus recheck if spicesArray has anything in it you must use state and make a change to state.

    const [spiceArray, setSpiceArray] = useState<singleSpice[] | null>(null)
      useEffect(() => {
        //fetch the data --> Works well
        async function loadData() {
          const data = await getCollections();
        // Add the Data to the Spicearray --> Works  
         setSpiceArray(data)
        }
        // Trigger Function onComponentMount
        loadData();
        
        // This function will never log "Worked" even tho the loadData is Async
        if (spiceArray.length > 0) {
          console.log("Worked");
        }
      }, []);
    

    your code should look something like this and change child component rendering logic to:

    {spices && spices.map(entry => <IonItem id={entry.spice}>{entry.spice}</IonItem>)}
    

    Keep in mind a couple of other things:

    1. if you want useEffect to run only on mount (which is usually the case when fetching data), you need an empty dependency array.
    2. console logging the spiceArray or checking if the spiceArray.length is not 0 will still not work in useEffect because state updates in react are async/ a request to update state for the next render. Therefor, changes will not be immediately reflected/ observable