3

I would like to create a "go back" button that only goes back one page if the page is within the site.

I've tried following this answer to add a return button:

import { useNavigate } from 'react-router-dom';

function YourApp() {
  const navigate = useNavigate();

  return (
    <>
      <button onClick={() => navigate(-1)}>go back</button>
    </>
  );
}

But it goes one page back even if the page is not within the site.

E.g.: if I open a tab, go to stackoverflow.com, then go to my page and click the "go back" button, I will come back to StackOverflow.

How can I make it send me to a default page if the previous page is not within the site?

Asghwor
  • 353
  • 5
  • 16
  • This is the intended behavior. The `navigate` function with a delta argument only navigates forward/backward through the history stack. Since this stack isn't exposed out to *your* code via RRD you'd need to keep and maintain your own history stack and conditionally handle the back navigation when the stack is empty. That said, it's generally considered poor UI/UX to trap users in your page/app. – Drew Reese Jul 14 '22 at 15:56
  • @drew-reese I don't agree that's a bad practice to have a "return" button in a app take the user to the same app. I think that's the expected behavior. – Asghwor Jul 15 '22 at 17:00
  • Right... I think calling it "go back" is a little misleading. If I click a "go back" button and *don't* go back to the place I was prior, that's a bad experience. I'm working on a possible solution/demo right now though, actually. – Drew Reese Jul 15 '22 at 17:02
  • @drew-reese I mean, I think it might be nicer to change the label of the button to "go to " when there is no previous page within the app. OTOH, I think the user can tell that, if they typed `mysite.com/pages/create` in the URL bar and there is a return button in the page, it doesn't mean "go back to the previous website". – Asghwor Jul 15 '22 at 17:11
  • Does this answer your question? [How to know if react-router can go back to display back button in react app](https://stackoverflow.com/questions/37385570/how-to-know-if-react-router-can-go-back-to-display-back-button-in-react-app) – adrihanu Nov 16 '22 at 06:50

2 Answers2

0

I ended up solving this by creating a history array in Redux and feeding it the current location from useLocation whenever it changes:

export const MyRoutes = () => {
  const location = useLocation();
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(pushLocationToHistory(location));
  }, [location]);
};

And then I can just use the path of the last item if available.

Asghwor
  • 353
  • 5
  • 16
0

The basic gist is that you can keep and maintain your own history stack and check if there are history entries to go back to. If the stack is empty then redirect to the app's home page, otherwise, allow the normal navigation.

Example using a React Context to hold the stack state and provide a customized navigate function.

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useState
} from "react";
import {
  useNavigate as useNavigateBase,
  UNSAFE_NavigationContext,
  NavigationType
} from "react-router-dom";

const NavigateContext = createContext({
  navigate: () => {},
});

const useNavigate = () => useContext(NavigateContext);

const NavigateProvider = ({ children }) => {
  const [historyStack, setHistoryStack] = useState([]);

  const navigateBase = useNavigateBase();

  const { navigator } = useContext(UNSAFE_NavigationContext);

  useEffect(() => {
    const listener = ({ location, action }) => {
      switch (action) {
        case NavigationType.Push:
          return setHistoryStack((stack) => stack.concat(location));

        case NavigationType.Replace:
          return setHistoryStack((stack) =>
            stack.slice(0, -1).concat(location)
          );

        case NavigationType.Pop:
          return setHistoryStack((stack) => stack.slice(0, -1));

        default:
        // ignore
      }
    };

    return navigator.listen(listener);
  }, [navigator]);

  useEffect(() => {
    console.log({ historyStack });
  }, [historyStack]);

  const navigate = useCallback(
    (arg, options) => {
      if (typeof arg === "number" && arg < 0 && !historyStack.length) {
        navigateBase("/", { replace: true });
      } else {
        navigateBase(arg, options);
      }
    },
    [historyStack, navigateBase]
  );

  return (
    <NavigateContext.Provider value={navigate}>
      {children}
    </NavigateContext.Provider>
  );
};

Example Usage:

const GoBackButton = () => {
  const navigate = useNavigate();
  return <button onClick={() => navigate(-1)}>go back</button>
}

...

function App() {
  return (
    <NavigateProvider>
      ... app code ...
    </NavigateProvider>
  );
}

Edit how-to-go-back-one-page-only-if-the-page-is-within-the-site-in-react-router-dom

Drew Reese
  • 165,259
  • 14
  • 153
  • 181