Yan
Yan

Reputation: 81

how do i delay rendering of a page until my data is available?

I have an issue where my page is trying to render before the data is available. I have async awaits in place, however, the page gets an error saying data is undefined. When I comment out my page elements and check react dev tools I can see the data object in full, so I know the data request is working.

I need to put in a check for the data and if present then render but as a new developer I am not sure how to implement this in my code.

import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getDoc, doc } from "firebase/firestore";
import { db } from "../api/auth/firebase/config";

import Head from "next/head";
import ArtistHeader from "../../components/ArtistHeader";
import UploadButton from "../../components/UploadButton";
import styles from "../../styles/artistPage.module.css";

export default function Artist() {
  const { data: session, status, loading } = useSession();
  const [artist, setArtist] = useState();
  const router = useRouter();
  const artistId = router.query.artistId;

  const fetchArtist = async () => {
    const artistRef = doc(db, "users", `${artistId}`);
    const docSnap = await getDoc(artistRef);
    setArtist(docSnap.data());
  };

  useEffect(() => {
    if (!router.isReady) return;
    console.log(artistId);
    if (status === "unauthenticated") {
      router.push("/auth/signin");
    }
    fetchArtist();
  }, [status, loading, router]);

  return (
    <section className={styles.wrapper}>
      <Head>
        <title>{artist.screenName}</title>
      </Head>
      <div className={styles.artistPage}>
        <ArtistHeader artist={artist} />
        <div className={styles.songContainer}>
          <UploadButton />
        </div>
      </div>
    </section>
  );
}

Thanks in advance for help.

Upvotes: 1

Views: 2260

Answers (3)

Adiat Hasan
Adiat Hasan

Reputation: 411

use optional chaining. This will prevent you from getting undefined error.

see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining

On the other hand you can do the following:

const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)

const fetchArtist = async () => {
    setIsLoading(true)
    const artistRef = doc(db, "users", `${artistId}`);
    
    try{
      const docSnap = await getDoc(artistRef);
      setArtist(docSnap.data());
    }catch(e){
      setIsError(true)
    }
    
    setIsLoading(false)
};

if(isLoading && !artist){
  return (
    <h2>Loading...</h2>
  )
}

if(!isLoading && isError){
  return (
    <h2>Something went wrong</h2>
  )
}


return (
    <section className={styles.wrapper}>
      <Head>
        <title>{artist?.screenName}</title>
      </Head>
      <div className={styles.artistPage}>
        <ArtistHeader artist={artist} />
        <div className={styles.songContainer}>
          <UploadButton />
        </div>
      </div>
     </section>
)

But I would prefer react-query for server state management. It handles all your loading | revalidation | caching and more.

Check out https://tanstack.com/query/v4/docs/adapters/react-query

Let's make it simple with useQuery hook from react-query

import { useQuery } from '@tanstack/react-query'

const fetchArtist = async (artistId: string) => {
    const artistRef = doc(db, "users", `${artistId}`);
    return getDoc(artistRef);
};

function Artist() {
  const query = useQuery(['artist', artistId], fetchArtist)

  const {isLoading, isError, data} = query 

  if(isLoading){
    return (
      <h2>Loading...</h2>
    )
  }

  if(isError && !data){
    return (
      <h2>Something went wrong</h2>
    )
  }

  return (
    <section className={styles.wrapper}>
      <Head>
        {/* optional chaining (?.) */}
        <title>{data?.artist?.screenName}</title>
      </Head>
      <div className={styles.artistPage}>
        <ArtistHeader artist={data?.artist} />
        <div className={styles.songContainer}>
          <UploadButton />
        </div>
      </div>
    </section>
  )
}


// _app.jsx
import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query'

export default function MyApp({ Component, pageProps }) {
  const [queryClient] = React.useState(() => new QueryClient())

  return (
    <QueryClientProvider client={queryClient}>
      <Hydrate state={pageProps.dehydratedState}>
        <Component {...pageProps} />
      </Hydrate>
    </QueryClientProvider>
  )
}

Upvotes: 1

Nick Vu
Nick Vu

Reputation: 15540

You can use getServerSideProps to call API on the server. Whenever data is ready, the page will start loading on the client-side.

import React, { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { getDoc, doc } from "firebase/firestore";
import { db } from "../api/auth/firebase/config";

import Head from "next/head";
import ArtistHeader from "../../components/ArtistHeader";
import UploadButton from "../../components/UploadButton";
import styles from "../../styles/artistPage.module.css";

export default function Artist({ data }) {
  const { data: session, status, loading } = useSession();
  const artist = data; //get data from the server
  const router = useRouter();

  useEffect(() => {
    if (status === "unauthenticated") {
      router.push("/auth/signin");
    }
  }, [status, loading, router]);

  return (
    <section className={styles.wrapper}>
      <Head>
        <title>{artist.screenName}</title>
      </Head>
      <div className={styles.artistPage}>
        <ArtistHeader artist={artist} />
        <div className={styles.songContainer}>
          <UploadButton />
        </div>
      </div>
    </section>
  );
}
export async function getServerSideProps(context) {
  const artistId = context.params.artistId; 
  const artistRef = doc(db, "users", `${artistId}`);
  const docSnap = await getDoc(artistRef);
  const data = docSnap.data();

  return { props: { data: data || null } }
}

Upvotes: 1

Arifur Rahaman
Arifur Rahaman

Reputation: 574

You can use a state isLoading. The initial value of isLoading will be false. Inside useEffect before fetching data set isLoading value as true and after completing fetching set isLoading as false. Now use conditional rendering if isLoading then render a Loader component else render jsx with data.

Upvotes: 0

Related Questions