history.goBack goes back in browser history, not to the previous website page

Created on 23 Mar 2018  路  13Comments  路  Source: ReactTraining/history

I am using react-router, and have a back button that works only if this.props.history.length >= 1. If I browse my website for some time, then close it and open in another tab (which already had something else open), back button (pressed immediately) would take me back to something else (some other website), instead of having this.props.history.length a value like 0 or 1. Am I misunderstanding something, or it is really a bug?

Most helpful comment

I'm experiencing this issues as well.

Use case: If history.go(-1) would keep user within the current web-app use it, otherwise push/replace some other predefined location.
This has the benefit of saving scroll position and more accurately represents where user is in history so that clicking the back button works as expected.

I've dug into the code and the only way I can figure is to track the current location manually. Something like this:

This works when using history.push, history.goBack, and history.replace.

However not enough information is passed in the listener to accurately predict where history.goForward, history.go(-2), or any other larger jumps.

Any thoughts on this are appreciated.

import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

const pastLocations: Location[] = [];
function updatePastLocations(location: Location, action: Action) {
    switch (action) {
      case 'PUSH':
        // first location when app loads and when pushing onto history
        this.pastLocations.push(location);
        break;
      case 'REPLACE':
        // only when using history.replace
        this.pastLocations[this.pastLocations.length - 1] = location;
        break;
      case 'POP': {
        // happens when using the back button, or forward button
        this.pastLocations.pop();
        // location according to pastLocations
        const appLocation = this.pastLocations[this.pastLocations.length - 1];
        if (!(appLocation && appLocation.key === location.key)) {
          // If the current location doesn't match what the app thinks is the current location,
          // blow up the app history.
          this.pastLocations = [location];
        }
        break;
      }
      default:
    }
}
history.listen(updatePastLocations);

function isPreviousLocationWithinApp(): boolean {
  return pastLocations.length > 1;
}

export function goBackOrReplace(
  location: H.Path | LocationDescriptorObject,
  state?: H.LocationState
): void {
  if (isPreviousLocationWithinApp()) {
    history.goBack();
  } else {
    history.replace(location as any, state);
  }
}
  • updated with actual implementation

All 13 comments

Each tab maintains its own joint session history (essentially an array of locations).

jointHistory = [
  'example.com/one',
  'example.com/two',
  'evil.com/one',
];

Within the joint session history, each site you visit has its own session history (a slice of the array).

exampleSession = [
  'example.com/one',
  'example.com/two'
]
evilSession = [
  'evil.com/one'
];

When you open the site in a new tab, it exists in a new session and has no access to a different tab's session history. I would ignore history.length. This is the joint session's length and from my experience it is rarely useful (it maxes out at 50, so it isn't even usable with long sessions).

There are actually a number of different little history quirks (for security, so not _really_ a quirk). For example, if you are on your site and navigating around and then you enter a new URL in the address bar for another page on your own site, that will create a new session.

jointHistory = [
  'example.com/one',
  'example.com/two',
  // enter example.com/three in address bar
  'example.com/three'
];

sessionOne = [
  'example.com/one',
  'example.com/two',
]
sessionTwo = [
  'example.com/three'
]

Thanks! Is there any way to access local history then? I am trying to make stack navigation using history, but now I am not sure if that is really possible.

I'm not exactly sure what you mean by stack navigation.

For security reasons, you cannot read the locations in the session history besides the one you're currently at. You could potentially store locations in memory as you visit them, but this would have issues if you navigate away from a site and go back to it using the browser's back button. Session storage could potentially be used for this, but I don't think it is functionality that the history package will implement itself.

I meant getting local history length or otherwise making sure that history.goBack will lead to the same website, and not some other.

Yeah, there isn't really a good solution for that. You could potentially use location.state to attach index information, but I've never seen someone do this, so I'm not sure what pitfalls it would have. Using the history.length when the app loads might also help, but as I said earlier, there are issues with sessions with over 50 entries, so that wouldn't be bulletproof.

So basically there is no way to see if history.goBack works?

Nope. The go methods are just wrappers around the native window.history.go method.

I'm experiencing this issues as well.

Use case: If history.go(-1) would keep user within the current web-app use it, otherwise push/replace some other predefined location.
This has the benefit of saving scroll position and more accurately represents where user is in history so that clicking the back button works as expected.

I've dug into the code and the only way I can figure is to track the current location manually. Something like this:

This works when using history.push, history.goBack, and history.replace.

However not enough information is passed in the listener to accurately predict where history.goForward, history.go(-2), or any other larger jumps.

Any thoughts on this are appreciated.

import { createBrowserHistory } from 'history';

export const history = createBrowserHistory();

const pastLocations: Location[] = [];
function updatePastLocations(location: Location, action: Action) {
    switch (action) {
      case 'PUSH':
        // first location when app loads and when pushing onto history
        this.pastLocations.push(location);
        break;
      case 'REPLACE':
        // only when using history.replace
        this.pastLocations[this.pastLocations.length - 1] = location;
        break;
      case 'POP': {
        // happens when using the back button, or forward button
        this.pastLocations.pop();
        // location according to pastLocations
        const appLocation = this.pastLocations[this.pastLocations.length - 1];
        if (!(appLocation && appLocation.key === location.key)) {
          // If the current location doesn't match what the app thinks is the current location,
          // blow up the app history.
          this.pastLocations = [location];
        }
        break;
      }
      default:
    }
}
history.listen(updatePastLocations);

function isPreviousLocationWithinApp(): boolean {
  return pastLocations.length > 1;
}

export function goBackOrReplace(
  location: H.Path | LocationDescriptorObject,
  state?: H.LocationState
): void {
  if (isPreviousLocationWithinApp()) {
    history.goBack();
  } else {
    history.replace(location as any, state);
  }
}
  • updated with actual implementation

@IanEdington if you refresh, your page will go pre-page. because refresh the action is 'POP'.

@longmajuxie If the app is refreshed it will blow away the pastLocations array and start a new history.

@IanEdington you can also use sessionStorage to keep browser history on refresh

import {
  Action,
  createBrowserHistory,
  Location,
  LocationState,
  Path
} from "history";

export const history = createBrowserHistory();

class PathLocationManager {
  private pastLocations: Location[] = [];
  private readonly key = "appLocationHistory";

  constructor() {
    const jsonFromSessionStorage = sessionStorage.getItem(this.key);
    this.pastLocations = jsonFromSessionStorage
      ? (JSON.parse(jsonFromSessionStorage) as Location[])
      : [];
  }

  public push(location: Location) {
    this.pastLocations.push(location);
    this.dumpToSessionStorage();
  }

  public pop() {
    this.pastLocations.pop();
    this.dumpToSessionStorage();
  }

  public length() {
    return this.pastLocations.length;
  }

  public setLocation(index: number, location: Location) {
    this.pastLocations[index] = location;
    this.dumpToSessionStorage();
  }

  public getLocation(index: number) {
    return this.pastLocations[index];
  }

  public setLocations(locations: Location[]) {
    this.pastLocations = locations;
    this.dumpToSessionStorage();
  }

  private dumpToSessionStorage() {
    sessionStorage.setItem(this.key, JSON.stringify(this.pastLocations));
  }
}

const pastLocations = new PathLocationManager();

function updatePastLocations(location: Location, action: Action) {
  switch (action) {
    case "PUSH":
      // first location when app loads and when pushing onto history
      pastLocations.push(location);
      break;
    case "REPLACE":
      // only when using history.replace
      pastLocations.setLocation(pastLocations.length() - 1, location);
      break;
    case "POP": {
      // happens when using the back button, or forward button
      pastLocations.pop();
      // location according to pastLocations
      const appLocation = pastLocations.getLocation(pastLocations.length() - 1);
      if (!(appLocation && appLocation.key === location.key)) {
        // If the current location doesn't match what the app thinks is the current location,
        // blow up the app history.
        pastLocations.setLocations([location]);
      }
      break;
    }
    default:
  }
}
history.listen(updatePastLocations);

function isPreviousLocationWithinApp(): boolean {
  return pastLocations.length() > 1;
}

export function goBackOrPush(location: Path, state?: LocationState): void {
  if (isPreviousLocationWithinApp()) {
    history.goBack();
  } else {
    history.push(location, state);
  }
}

But it still has the bug, so when the user visits pages in such direction:

goBackOrPush will go to the google.com. Not sure how it can be solved 馃

Updated:

We can use referer! So if referer is not your domain - reset the sessionStorage 馃 But still not sure that it will work smoothly

I would ignore history.length. This is the joint session's length and from my experience it is rarely useful (it maxes out at 50, so it isn't even usable with long sessions).

That's the real takeaway here. history.length is not reliable and should probably be removed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pshrmn picture pshrmn  路  7Comments

clayrisser picture clayrisser  路  4Comments

richardgorman picture richardgorman  路  5Comments

vlzuiev picture vlzuiev  路  4Comments

Carduelis picture Carduelis  路  6Comments