Next.js中如何实现仅在router.query.id变化时触发且可调用router.push的useEffect
Great question! The core issue here is that Next.js's useRouter hook returns a fresh router object every time any part of the route changes—including query parameters like page. This means the push method’s reference updates on every route tweak, causing your useEffect to re-run even when id stays the same.
Best Approach: Use useRef to Hold a Stable Router Reference
The cleanest way to avoid this is to store the router instance in a ref, which maintains a stable reference across re-renders. This lets you call push safely without adding it to your useEffect’s dependency array.
Here's how to implement it:
import { useRouter, useEffect, useRef } from 'next/router'; const YourComponent = () => { const router = useRouter(); const routerRef = useRef(router); const { id } = router.query; // Keep the ref updated with the latest router instance useEffect(() => { routerRef.current = router; }, [router]); useEffect(() => { // Guard clause: Exit if id is undefined (common during hydration) if (!id) return; try { fetchDataFor(id); } catch (err) { // Use the stable router reference to push routerRef.current.push('/not-found'); } }, [id]); // Only re-run when id changes—perfect! // ... rest of your component };
Why This Works
- The
routerRefholds the latest router instance at all times, thanks to the small useEffect that updates it wheneverrouterchanges. - Your main useEffect only depends on
id, so it will only trigger whenidchanges—exactly what you need. - You avoid the anti-pattern of ignoring dependencies with
eslint-disable-next-line, which can lead to stale closures and hard-to-debug bugs later.
Why Other Options Fall Short
- Removing
pushfrom dependencies: This is an anti-pattern because it can cause your useEffect to use a stalepushreference, leading to unexpected behavior if the router state changes. - Dynamic routes: Even if you switch to
/some-route/[id], changing thepagequery param will still create a new router object, sopush’s reference will update and your useEffect will re-run unnecessarily. - Wrapping
pushinuseCallback: This doesn’t solve the problem because the callback’s dependency onroutermeans it will still update every time the route changes, triggering the useEffect.
Bonus: Handling Hydration Edge Cases
Note the guard clause if (!id) return—this is important because Next.js hydrates components on the client, and router.query.id might be undefined briefly during that process. Adding this prevents unnecessary errors or fetch calls.
内容的提问来源于stack exchange,提问作者cbdeveloper

