Building a Custom 'Go Back' Button in React is Harder than you think

Building a custom “go back” button shouldn’t be hard, right? right?… 😟 Most developers would start with something like this:

const navigate = useNavigate();
const onClickBack = () => navigate(-1)

You test it, it seems to be working fine, until some user says it’s not…

Before talking about when it’ll break, let’s quickly understand what navigate(-1) means.

It means: go back one item in the browser’s history stack. The history stack is last-in-first-out list of the pages that the user navigated between. Each time a user goes to a new page, a new entry is pushed on top of the stack.

Okay, so when does it break??

If the user didn’t navigate to the page from another page, but opened the link to it directly (copy pasted it, or clicked on it from somewhere else), then the history stack will be empty! So calling navigate(-1) at this point won’t work as expected. (Most likely it won’t do anything at all)

And this can actually be REALLY BAD if the page the user lands on doesn’t have any other way to navigate to other pages except for this broken back button! (Cause it’s expecting the back button must always be working 🙃)

So what’s the solution??

It depends on our case.

Something I usually do is: if the user lands directly on this page & he clicks the “go back” button, (& the history is empty), I take him to some “fallback” page which is usually the page he most likely would have been on before this page (e.g. home page, products list page, ...etc).

So basically we check whether the user landed on this page directly or not.

Yes ⇒ navigate(-1) No ⇒ navigate(fallbackUrl)

“Are we done??”

Unfortunately nope. We still have another broken case that we need to handle…

1- If the user lands on a page directly, 2- clicks our custom back button which will take him to our fallback page, what does the history stack contains at this point? A single entry, the page we landed on initially.

3- So if he clicks the back button again (custom or the browser’s), it’ll take him back to that page, & the history stack will become empty again.

4- And it's like we are back at step 1… 😶

Basically locking the user in a loop…

Thankfully, we can solve this simply by making sure to remove the page entry from the history stack when clicking on our custom back button. So clicking the back button later won’t bring us to it again. (That’s what the browser’s back button does too)

We do this by just passing replace: true option to the navigate function.

And we are done. 😤 Definitely took more effort than what one would initially think.

I usually encapsulate all of the above in a custom useNavigateBack hook, here is the code:

export const useNavigateBack = (fallbackUrl: string) => {
  const location = useLocation();
  const navigate = useNavigate();

  const locationKey = location.key;

  const navigateBack = useCallback(() => {
    if (locationKey === "default")
      // this tells us that the user landed on this page directly
      navigate(fallbackUrl, { replace: true });
    else navigate(-1);
  }, [fallbackUrl, locationKey, navigate]);

  return navigateBack;
};

Hopefully this saves you from wasting a lot of time & effort on figuring it out.

Until next time 👋

My Photo

About Me

I'm a freelance web developer who specializes in frontend.
I have a special love for React. And my personal goal is: Building things that are Awesome! ✨
If you are someone who is building a project where high quality is a MUST, then Let's Chat! I'll be glad to help you with it.

© 2023-present Mohammed Taher Ghazal. All Rights Reserved.