Reputation: 2738
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 }
}
Upvotes: 1
Views: 3180
Reputation: 46171
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 };
};
Upvotes: 3
Reputation: 6081
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])
Upvotes: 1
Reputation: 804
If you make minimal representation, possible to try fix this error
Upvotes: 0