Route interception
Answered
Austrian Black and Tan Hound posted this in #help-forum
Austrian Black and Tan HoundOP
How the duck do we prevent an intercepted route from showing when you update the search params from the page that was intercepted?
Answered by Austrian Black and Tan Hound
I had to create this silly component to handle whether or not the route should be blocked:
//RouteInterceptBlocker.tsx
"use client";
import { usePathname } from "next/navigation";
import { useRef, createContext, useContext, type ReactNode } from "react";
type RouteInterceptBlockerContextValue = {
shouldBlock: (path: string) => boolean;
};
const RouteInterceptBlockerContext =
createContext<RouteInterceptBlockerContextValue | null>(null);
export const useRouteInterceptBlocker = () => {
const context = useContext(RouteInterceptBlockerContext);
if (!context) {
throw new Error(
"RouteInterceptBlockerProvider must be used within a RouteInterceptBlockerContext",
);
}
return context;
};
export const RouteInterceptBlockerProvider = ({
children,
}: { children?: ReactNode }) => {
const pathnameRef = useRef(usePathname());
const shouldBlock = (path: string) => {
const currentPathname = pathnameRef.current;
return currentPathname.startsWith(path);
};
return (
<RouteInterceptBlockerContext.Provider value={{ shouldBlock }}>
{children}
</RouteInterceptBlockerContext.Provider>
);
};
//app/@aside/(.)search/components.tsx
const _Drawer = ({ children }: { children: ReactNode }) => {
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
useLayoutEffect(() => {
setIsOpen(true);
}, []);
const handleClose = useEffectEvent(() => {
if (!isOpen) {
router.back();
}
});
return (
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<AnimatePresence onExitComplete={handleClose}>
{isOpen && <Dialog.Portal forceMount>{children}</Dialog.Portal>}
</AnimatePresence>
</Dialog.Root>
);
};
export const Drawer = ({ children }: { children: ReactNode }) => {
const pathname = usePathname();
const { shouldBlock } = useRouteInterceptBlocker();
const block = shouldBlock("/search");
return !block && pathname === "/search" && <_Drawer>{children}</_Drawer>;
};
// app/@aside/(.)search/layout.tsx
import * as Search from "./components";
export default function SearchLayout({ children }: { children: ReactNode }) {
return (
<Search.Drawer>
{/* other layout stuff /*}
{children}
</Search.Drawer>
)
}6 Replies
Austrian Black and Tan HoundOP
I had to create this silly component to handle whether or not the route should be blocked:
//RouteInterceptBlocker.tsx
"use client";
import { usePathname } from "next/navigation";
import { useRef, createContext, useContext, type ReactNode } from "react";
type RouteInterceptBlockerContextValue = {
shouldBlock: (path: string) => boolean;
};
const RouteInterceptBlockerContext =
createContext<RouteInterceptBlockerContextValue | null>(null);
export const useRouteInterceptBlocker = () => {
const context = useContext(RouteInterceptBlockerContext);
if (!context) {
throw new Error(
"RouteInterceptBlockerProvider must be used within a RouteInterceptBlockerContext",
);
}
return context;
};
export const RouteInterceptBlockerProvider = ({
children,
}: { children?: ReactNode }) => {
const pathnameRef = useRef(usePathname());
const shouldBlock = (path: string) => {
const currentPathname = pathnameRef.current;
return currentPathname.startsWith(path);
};
return (
<RouteInterceptBlockerContext.Provider value={{ shouldBlock }}>
{children}
</RouteInterceptBlockerContext.Provider>
);
};
//app/@aside/(.)search/components.tsx
const _Drawer = ({ children }: { children: ReactNode }) => {
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();
useLayoutEffect(() => {
setIsOpen(true);
}, []);
const handleClose = useEffectEvent(() => {
if (!isOpen) {
router.back();
}
});
return (
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<AnimatePresence onExitComplete={handleClose}>
{isOpen && <Dialog.Portal forceMount>{children}</Dialog.Portal>}
</AnimatePresence>
</Dialog.Root>
);
};
export const Drawer = ({ children }: { children: ReactNode }) => {
const pathname = usePathname();
const { shouldBlock } = useRouteInterceptBlocker();
const block = shouldBlock("/search");
return !block && pathname === "/search" && <_Drawer>{children}</_Drawer>;
};
// app/@aside/(.)search/layout.tsx
import * as Search from "./components";
export default function SearchLayout({ children }: { children: ReactNode }) {
return (
<Search.Drawer>
{/* other layout stuff /*}
{children}
</Search.Drawer>
)
}Answer
Austrian Black and Tan HoundOP
The above was the only way I could figure this one out. It's important to note that the apps RootLayout should be wrapped with
RouteInterceptBlockerProvider:// app/layout.tsx
import { RouteInterceptBlockerProvider } from "@/components/RouteInterceptBlocker";
export default function RootLayout({
children,
aside,
}: {
children: ReactNode;
aside: React.ReactNode;
}) {
return (
<RouteInterceptBlockerProvider>
<html>
<body>
{children}
{aside}
</body>
</html>
</RouteInterceptBlockerProvider>
)
}Austrian Black and Tan HoundOP
I'm sorry the above actually doesn't seem to work when updating the url.
The intercepted route prevents the actual page from receiving updates.
Austrian Black and Tan HoundOP
This solution did not work, I put in issue up on nextjs -> https://github.com/vercel/next.js/issues/86362
My solution rn is to intercept the route that I want and then create the other "interceptable" page on a whole other route. To handle hard refreshes I have a GET route for the route which redirects you to the route I want to act as the hard route.