history icon indicating copy to clipboard operation
history copied to clipboard

[feature request] Smarter goBack

Open jankaifer opened this issue 4 years ago • 3 comments

React router had in previous versions this feature that you could ask whether goBack leaves your app or not. I do implement cancel buttons with goBack. But If someone gets there from a link on another site it just throws them back which is usually not desired.

I worked around this by adding fromApp state prop that would indicate that it is safe to use goBack. But that feels kinda hackish.

jankaifer avatar May 07 '20 15:05 jankaifer

Example

  • Open https://twitter.com/ReactTraining/status/1271508493896318977
  • Press the back button at the top of the page
  • It will redirect you to /home as there's nowhere to go back in the History API (instead of naively redirecting you to some other website you had in the tab before opening the link above)

Suggested solution

It would be nice if goBack had an optional fallback argument. So suppose we called history.goBack('/home'). In case going back would leave the website, it would call replace with the given fallback instead.

Workaround

I've been using a wrapper that covers my particular use case. The fallback argument only accepts a path string and it's not even optional, but you'll get the idea:

import {
  BrowserHistoryBuildOptions,
  History as OriginalHistory,
  createBrowserHistory,
} from 'history'

const initialLocationKey = '__INITIAL_LOCATION__'

export interface History extends Omit<OriginalHistory, 'goBack'> {
  goBack: (fallback: string) => void
}

export const createHistory = (options?: BrowserHistoryBuildOptions): History => {
  const history = createBrowserHistory(options) as History

  // Put a flag on the state of the initial location. We'll check for this flag
  // whenever we need to know whether the current location is the initial one in
  // the browser history.
  history.replace({
    ...history.location,
    state: {
      [initialLocationKey]: true,
    },
  })

  history.goBack = (fallbackPath: string) => {
    const state = history.location.state as any

    // Are we in the initial location? In case so, 'goBack' would originally
    // make us leave the website. Instead, let's use 'replace' to go to the
    // fallback path.
    if (state && state[initialLocationKey]) {
      history.replace(fallbackPath, state)
      return
    }

    window.history.go(-1)
  }

  return history
}

gustavopch avatar Jun 12 '20 19:06 gustavopch

I just found out this lib keeps an idx property in the window.history.state which will be 0 when the current route is the first route since the web app has been mounted. So I'm able to use the following custom hook with React Router v6:

import { To } from 'history'
import { useCallback } from 'react'
import { useNavigate } from 'react-router-dom'

export const useGoBack = () => {
  const navigate = useNavigate()

  const goBack = useCallback(
    (fallback: To) => {
      // Going back would leave the website?
      if (window.history.state.idx === 0) {
        navigate(fallback, { replace: true })
        return
      }

      navigate(-1)
    },
    [navigate],
  )

  return goBack
}

Usage:

const goBack = useGoBack()

const handleClose = useCallback(() => {
  goBack('/home')
})

gustavopch avatar Jun 14 '20 20:06 gustavopch

@gustavopch I'm not seeing idx on window.history.state, not sure that's the case with the latest version

jedwards1211 avatar Nov 20 '20 02:11 jedwards1211