Conditional back button in nextjs
Asked Answered
F

2

2

I have an page '/message/:id' with a back button on top left which routes back using router.back() function, Which works perfectly fine when i get routed to that page from any other screen. But now i'm also sending notification which on click routes to '/message/:id' and since the page it fresh page and has no history to go back the back button doesn't work.

Is it possible to conditionally route back i.e. if there is any history then use router.back() or it should redirect me to home page ('/') Something like this

if(has page history) {
    router.back();
} else {
    router.redirect('/');
}
Found answered 19/7, 2023 at 22:55 Comment(0)
S
2

As far as I know there is no way to check previous entries in the history using the window object due to privacy concerns. A possible solution is to create a client component that will modify a context' store value upon client side navigation.


Client Component

This component creates a context that will be set to true when the user uses client side navigation. Therefore, if the context is truthy, the user accesses the next page from within your page. I have found a very good solution to track client side navigation changes with Next.js 13.

"use client";

import { useEffect, useState, createContext } from "react";

export const OriginContext = createContext<boolean>(false);

export default function OriginTracker({ children }: React.PropsWithChildren) {
  const [isWithinPage, setIsWithinPage] = useState(false);

  // track if the url has changed here (check link)

  return (
    <OriginContext.Provider value={isWithinPage}>
      {children}
    </OriginContext.Provider>
  );
}

In your root layout file

Wrap your layout content inside the OriginTracker component. Don't worry this does not mean that everything is going to be rendered client side, since client components can receive pre-rendered server components as children.

import OriginTracker from "components/OriginTracker";

export default function RootLayout({ children }: React.PropsWithChildren) {
  return <OriginTracker>{children}</OriginTracker>;
}

Inside some other component

Inside your other client components you can use the useContext hook to access the global state to determine where to navigate the user.

import { useCallback } from "react";
import { useRouter } from "next/navigation";

import { OriginContext } from "components/OriginTracker";

export default OtherComponent() {
  const router = useRouter();
  const isWithinPage = useContext(OriginContext);

  const onClick = useCallback(() => {
    if (isWithinPage) router.back();
    else router.replace('/');
  }, [isWithinPage, router]);

  return <button onClick={onClick}>Take me back</button>;
}
Sundial answered 20/7, 2023 at 11:53 Comment(2)
Thanks for the answer! This approach indeed worked, but i tried a slight change and worked for me. Attaching that solution too as an answer.Found
@DeepShetye Happy to help! Great job updating the post with your own answer, may help someone in the future!Sundial
F
2

This is the same answer given by Fabio Nettis with useEffect instead of hook to track route change.


Client Component

This component creates a context that will be set to true when the user uses client side navigation. Therefore, if the context is truthy, the user accesses the next page from within your page.

"use client";

import { usePathname } from 'next/navigation';
import { useState, createContext, useEffect, useRef } from 'react';

export const OriginContext = createContext<boolean>(false);

export default function OriginTracker({ children }: React.PropsWithChildren) {
  const [isWithinPage, setIsWithinPage] = useState(false);
  const isInitialLoadRef = useRef(true);

  useEffect(() => {
    if (isInitialLoadRef.current) {
      isInitialLoadRef.current = false;
      return;
    }

    setIsWithinPage(true);
    return () => setIsWithinPage(false);
  }, [pathname]);

  return (
    <OriginContext.Provider value={isWithinPage}>
      {children}
    </OriginContext.Provider>
  );
}

In your root layout file

Wrap your layout content inside the OriginTracker component. Don't worry this does not mean that everything is going to be rendered client side, since client components can receive pre-rendered server components as children.

import OriginTracker from "components/OriginTracker";

export default function RootLayout({ children }: React.PropsWithChildren) {
  return <OriginTracker>{children}</OriginTracker>;
}

Inside some other component

Inside your other client components you can use the useContext hook to access the global state to determine where to navigate the user.

import { useCallback } from "react";
import { useRouter } from "next/navigation";

import { OriginContext } from "components/OriginTracker";

export default OtherComponent() {
  const router = useRouter();
  const isWithinPage = useContext(OriginContext);

  const onClick = useCallback(() => {
    if (isWithinPage) router.back();
    else router.push('/');
  }, [isWithinPage, router]);

  return <button onClick={onClick}>Take me back</button>;
}
Found answered 22/7, 2023 at 8:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.