skip to Main Content

When I load my Nextjs page, I get this error message: "Error: Rendered more hooks than during the previous render."

If I add that if (!router.isReady) return null after the useEffect code, the page does not have access to the solutionId on the initial load, causing an error for the useDocument hook, which requires the solutionId to fetch the document from the database.

Therefore, this thread does not address my issue.

Anyone, please help me with this issue!

My code:

const SolutionEditForm = () => {
  const [formData, setFormData] = useState(INITIAL_STATE)
  const router = useRouter()
  const { solutionId } = router.query

  if (!router.isReady) return null

  const { document } = useDocument("solutions", solutionId)
  const { updateDocument, response } = useFirestore("solutions")

  useEffect(() => {
    if (document) {
      setFormData(document)
    }
  }, [document])

  return (
    <div>
     // JSX code
    </div>
  )
}

useDocument hook:

export const useDocument = (c, id) => {
  const [document, setDocument] = useState(null)
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    const ref = doc(db, c, id)
    const unsubscribe = onSnapshot(
      ref,
      (snapshot) => {
        setIsLoading(false)
        if (snapshot.data()) {
          setDocument({ ...snapshot.data(), id: snapshot.id })
          setError(null)
        } else {
          setError("No such document exists")
        }
      },
      (err) => {
        console.log(err.message)
        setIsLoading(false)
        setError("failed to get document")
      }
    )
    return () => unsubscribe()
  }, [c, id])

  return { document, isLoading, error }
}

3

Answers


    1. UseEffect cannot be called conditionally
    2. UseEffect is called only on the client side.

    If you make minimal representation, possible to try fix this error

    Login or Signup to reply.
  1. You cannot call a hook, useEffect, your custom useDocument, or any other after a condition. The condition in your case is this early return if (!router.isReady) returns null. As you can read on Rules of Hooks:

    Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns…

    Just remove that if (!router.isReady) returns null from SolutionEditForm and change useDocument as below.

    export const useDocument = (c, id) => {
      const [document, setDocument] = useState(null);
      const [isLoading, setIsLoading] = useState(true);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        if (!id) return; // if there is no id, do nothing 👈🏽
        const ref = doc(db, c, id);
        const unsubscribe = onSnapshot(
          ref,
          (snapshot) => {
            setIsLoading(false);
            if (snapshot.data()) {
              setDocument({ ...snapshot.data(), id: snapshot.id });
              setError(null);
            } else {
              setError("No such document exists");
            }
          },
          (err) => {
            console.log(err.message);
            setIsLoading(false);
            setError("failed to get document");
          }
        );
        return () => unsubscribe();
      }, [c, id]);
    
      return { document, isLoading, error };
    };
    
    Login or Signup to reply.
  2. The if (!router.isReady) return null statement caused the function to end early, and subsequent hooks are not executed.

    You need to restructure your hooks such that none of them are conditional:

    const [formData, setFormData] = useState(INITIAL_STATE)
      const router = useRouter()
      const { solutionId } = router.query
    
    
      const { document } = useDocument("solutions", solutionId, router.isReady) // pass a flag to disable until ready
      const { updateDocument, response } = useFirestore("solutions")
    
      useEffect(() => {
        if (document) {
          setFormData(document)
        }
      }, [document])
    
    // Move this to after the hooks.
      if (!router.isReady) return null
    

    and then to make useDocument avoid sending extra calls:

    export const useDocument = (c, id, enabled) => {
    

    and updated the effect with a check:

    useEffect(() => {
        if (!enabled) return;
        const ref = doc(db, c, id)
        const unsubscribe = onSnapshot(
          ref,
          (snapshot) => {
            setIsLoading(false)
            if (snapshot.data()) {
              setDocument({ ...snapshot.data(), id: snapshot.id })
              setError(null)
            } else {
              setError("No such document exists")
            }
          },
          (err) => {
            console.log(err.message)
            setIsLoading(false)
            setError("failed to get document")
          }
        )
        return () => unsubscribe()
      }, [c, id, enabled])
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search