WICG / navigation-api

The new navigation API provides a new interface for navigations and session history, with a focus on single-page application navigations.
https://html.spec.whatwg.org/multipage/nav-history-apis.html#navigation-api
484 stars 30 forks source link

Traversal direction from current state (outside events) #267

Open GriffinSauce opened 1 year ago

GriffinSauce commented 1 year ago

Hi, so first off: this API rocks.

I'm looking into implementing transition animations for my app, which is powered by React Router.

For a "slide over" animation I'd need to know the direction of traversal. Now this is pretty easy to determine in the event handlers by comparing indexes but because my routing is handled by another library it would be very helpful to be able to access this information statically as well. (to avoid race conditions between the event handling)

So we have:

I'm not sure if this steps too far outside of the scope of the API but I'd love to hear your opinions on this.

In terms of a solution, getting something like lastEntry would solve the problem, but perhaps a list of last visited entries (probably limited to reasonable number) could support more use cases.

tbondwilkinson commented 1 year ago

I think you can implement this pretty simply with a currententrychange listener.

const steps = [];
navigation.addEventListener('currententrychange', (e) => {
  if (e.navigationType === 'traversal') {
    steps.add({from: e.from, to: navigation.currentEntry});
  }
});

That would give you a list of {to, from} objects that represent every history change your application has observed.

GriffinSauce commented 1 year ago

Thanks @tbondwilkinson ! I was doing something similar, with the event handler you don't really need the steps, just the last one:

let lastNavigationEvent
window.navigation.addEventListener('currententrychange', event => {
  lastNavigationEvent = event
})

export const getLastNavigationDirection = (): 'unknown' | 'back' | 'forward' => {
  if (
    !lastNavigationEvent || // First landing
    lastNavigationEvent.navigationType === 'reload' ||
    lastNavigationEvent.navigationType === 'replace'
  ) {
    return 'unknown'
  }

  const lastIndex = lastNavigationEvent.from.index
  const currentIndex = navigation.currentEntry.index
  if (currentIndex === lastIndex) return 'unknown'
  if (currentIndex > lastIndex) return 'forward'
  if (currentIndex < lastIndex) return 'back'
}

(might have some rough edges, just exploring the ideas)

But the main thing I'm worried about is a race-condition between that event handler and the react-router event handler that triggers re-rendering and animations and I have no control over the latter (aside from maybe monkey-patching it).

So, the green path:

And the bug:

I guess this can be mitigated by making sure the order of attaching listeners is correct but that feels quite fragile to me. Should I be less worried about that?