r121
r121

Reputation: 2728

TypeError: Cannot read properties of null (reading 'useState') in Next.js

I'm getting this error message TypeError: Cannot read properties of null (reading 'useState') when I use my custom hooks inside the getStaticProps to fetch the data from the firebase firestore. Anyone, please help me with this?

Challenges page code:

import Card from "../components/reusable/Card"
import { useCollection } from "../hooks/useCollection"

const Challenges = ({ challenges }) => {
  return (
        <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8">
          {challenges.map((challenge) => {
                return (
                  <Card key={challenge.id} card={challenge} />
                )
              })}
        </div>
  )
}

export default Challenges

export async function getStaticProps() {
  const { documents, isLoading } = useCollection("challenges", null, null, [
    "createdAt",
    "desc",
  ])

  return {
    props: {
      challenges: documents,
    },
  }
}

useCollection hook code:

import { useEffect, useState } from "react"
import { collection, limit, onSnapshot, orderBy, query, where } from "firebase/firestore"

import { db } from "../firebase/config"

export const useCollection = (c, q, l, o) => {
  const [documents, setDocuments] = useState([])
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    let ref = collection(db, c)
    if (q) {
      ref = query(ref, where(...q))
    }
    if (o) {
      ref = query(ref, orderBy(...o))
    }
    if (l) {
      ref = query(ref, limit(l))
    }

    const unsubscribe = onSnapshot(ref, (snapshot) => {
      const results = []

      snapshot.docs.forEach(
        (doc) => {
          results.push({ ...doc.data(), id: doc.id })
        },
        (error) => {
          console.log(error)
          setError("could not fetch the data")
        }
      )
      // update state
      setDocuments(results)
      setIsLoading(false)
      setError(null)
    })

    return () => unsubscribe()
  }, [])

  return { documents, error, isLoading }
}

Upvotes: 2

Views: 6753

Answers (1)

Youssouf Oumar
Youssouf Oumar

Reputation: 45883

You can use useState or any hook only inside a React Component, or a custom hook as Rules of Hooks says, and getStaticProps is none of the two. Plus, it only runs at build time, so not sent to the browser:

If you export a function called getStaticProps (Static Site Generation) from a page, Next.js will pre-render this page at build time using the props returned by getStaticProps.

You could either move the data fetching to the client and get rid of getStaticPros, like so:

import Card from "../components/reusable/Card";
import { useCollection } from "../hooks/useCollection";

const Challenges = () => {
  const {
    documents: challenges,
    isLoading,
    error,
  } = useCollection("challenges", null, null, ["createdAt", "desc"]);

  if (isLoading) {
    return <p>Loading...</p>;
  }
  if (error) {
    return <p>Error happend</p>;
  }
  return (
    <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8">
      {challenges.map((challenge) => {
        return <Card key={challenge.id} card={challenge} />;
      })}
    </div>
  );
};

export default Challenges;

Or transform useCollection to a normal async function without any hook in it, and use it inside getStaticProps. But it doesn't seem like it's what you want, since you are creating a subscription on the client and all.

Upvotes: 2

Related Questions