Reputation: 81
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
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
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
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