Router.push(... {shallow:true}) on first page load gets cancelled.
Unanswered
Wuchang bream posted this in #help-forum
Wuchang breamOP
Having some trouble debugging an issue (with the pages router):
When my component first loads, I want to
When navigating to this page via an in-up link, this seems to work fine. However, on first page load -- it doesn't do anything at all!
By looking at the promise that
Ultimately, it seems like.. I'm too early in the rendering cycle -- and the page is still navigating (even though router isReady)? ...or something? If I add something like:
And wait for
When my component first loads, I want to
replaceState if certain conditions are met (if a parameter isn't present, I update the url with a default value). To do this, my useEffect hook looks something like:const isReady = useRouter.isReady()
useEffect(() => {
if(!isReady) return
if(someCondition) {
Router.push({query: "?param=foo"}, undefined, {shallow: true})
}
}, [isReady, someCondition])When navigating to this page via an in-up link, this seems to work fine. However, on first page load -- it doesn't do anything at all!
By looking at the promise that
Router.push returns, I was able to see that it resolves to false (a doc update might be useful to explain the possible return values of Router.push/replace; and when/why navigation is expected to be cancelled). Debugging through Next.js code seems to show that lastRenderReject is what is getting in the way of the router update. For context, here is the relevant library code ( https://github.com/vercel/next.js/blob/9fb18e673998635457903663205d10f6cc9b3849/packages/next/src/client/index.tsx#L654): if (lastRenderReject) {
lastRenderReject();
}
resolvePromise = ()=>{
lastRenderReject = null;
resolve();
}
;
lastRenderReject = ()=>{
canceled = true;
lastRenderReject = null;
const error = new Error("Cancel rendering route");
error.cancelled = true;
reject(error);
}Ultimately, it seems like.. I'm too early in the rendering cycle -- and the page is still navigating (even though router isReady)? ...or something? If I add something like:
const [isHydrated, setIsHydrated] = useState(false)
// Wait till Next.js rehydration completes
useEffect(() => {
setIsHydrated(true)
}, [])And wait for
isHydrated; that seems to be an effective workaround -- but I don't want to introduce it without first understanding why it's necessary.5 Replies
Wuchang breamOP
(just for context, credit for the one workaround I found mostly goes to https://medium.com/intelliconnect-engineering/fixing-hydration-issues-in-next-js-and-zustand-a-simple-solution-bd0a8deff6cc)
Wuchang breamOP
inital. I am updating a variable in my useEffect to prevent redirecting twice, and this is coming back to bite me.Something else is happening :/
Wuchang breamOP
ComponentDidMount in next.js is firing after my component's useEffect. Still not sure why. This seems to be causing a re-render of initial state and a hard navigation.Wuchang breamOP
I think I got it. Next.js
My new workaround:
ComponentDidMount is being called twice in react dev strict mode. ComponentDidMount has a side-effect that updates the router state + window location state (this is unexpected, at least by me). Because my useEffect also tries to update window location state, there is a conflict and it gets overridden. My new workaround:
const isReady = useRouter.isReady()
const pageInitialized = useRef(false)
useEffect(() => {
if (pageInitialized.current) return // already initialized.
if(!isReady) return
if(someCondition) {
Router.push({query: "?param=foo"}, undefined, {shallow: true}).then((val) => {
if (!val) pageInitialized.current = false // <---- this is the new code!
})
pageInitialized.current = true
}
} Wuchang breamOP
The above workaround code has a subtle bug. For at least one render, the component will consider itself initialized even though it will be reverted. A better fix is:
const isReady = useRouter.isReady()
const [isInitialized, setIsInitialized] = useState(false)
useEffect(() => {
if (!isReady || isInitialized) return
if (someCondition) {
Router.push({query: "?param=foo"}, undefined, {shallow: true}).then((success) => {
if (success) {
setIsInitialized(true)
} else {
Router.push({query: "?param=foo"}, undefined, {shallow: true}).then((success) => {
// second time is the charm...? :/
setIsInitialized(true)
}
}
})
}
}