react-router icon indicating copy to clipboard operation
react-router copied to clipboard

[Bug]: ViewTransitionContext locations are out of sync with the browser

Open ivany4 opened this issue 1 year ago • 1 comments

What version of React Router are you using?

6.23.0

Steps to Reproduce

Hi there!

I am using the context, in order to set up clues of navigation direction for specific view transitions, suggested here. It works with regular navigation, but gets quickly confused with browser history back/forward navigation. Pseudo code reproducer:

import { useContext } from 'react';
import { UNSAFE_ViewTransitionContext } from 'react-router-dom';

function useTracking() {
    const ctx = useContext(UNSAFE_ViewTransitionContext);
    if (ctx.isTransitioning === true) {
        console.log(`From ${ctx.currentLocation.pathname} to ${ctx.nextLocation.pathname}`);
    }
}

function PageA() {
     useTracking();
     return <NavLink to="/b" unstable_viewTransition />;
}

function PageB() {
    useTracking();
    return <NavLink to="/a" unstable_viewTransition />;
}

Now, if I start in Page A, go to Page B, and then press browser back button, it will console print twice From /a to /b. However, if at any point on Page B I will navigate to Page A by clicking NavLink, tracking seems to be restored (even when navigating using browser Back/Forward), and it will correctly print current and next locations.

I am testing this in Chromium 123.0.6312.105. Of course, I am not sure whether I am using the right tool for the task, but there's very little information on view transitions at the moment, so it's a guessing game.

Expected Behavior

Current and next location are consistently identified regardless of whether navigation is happening using links or browser history

Actual Behavior

Current and next location are becoming out of sync with the browser history

ivany4 avatar May 11 '24 14:05 ivany4

Update: In Google IO '24 they've announced view transition types, that help to achieve what I was trying to achieve with global classes here. More info: https://developer.chrome.com/blog/view-transitions-update-io24#view-transition-types. The hook from my example might not be needed anymore, but this new API will require an API update from react-router.

ivany4 avatar May 19 '24 22:05 ivany4

We are also looking to use view-transition-types with React Router. It would be great to have a built-in support for that.

noskovvkirill avatar Jan 18 '25 20:01 noskovvkirill

This was a big issue when implementing for us too. In particular, it seems not great that viewTransitions have to be enabled on a Link basis, meaning that hitting the browser back button does not trigger an animation.

What we did instead is hack our way around it by adding these in one of our top-level components:

const blocker = useBlocker(({ currentLocation, historyAction, nextLocation }) => {
	const currentPath = currentLocation.pathname;
	const nextPath = nextLocation.pathname;
	
	type NavType = 'DEEPER' | 'SHALLOWER' | 'ADJACENT';
	let navType: NavType;
	if (historyAction == 'REPLACE') {
		navType = 'ADJACENT';
	} else if (nextPath.indexOf(currentPath) == 0 && currentPath != nextPath) {
		navType = 'DEEPER';
	} else if (currentPath.indexOf(nextPath) == 0 && currentPath != nextPath) {
		navType = 'SHALLOWER';
	} else {
		navType = 'ADJACENT';
	}
	
	// Any adjacent navigation action will just be executed immediately
	if (navType == 'ADJACENT') return false;
	
        // Otherwise, specify which view transition to do. We do this with classes, but I'm sure view-transition-type would've been the better option
	document.body.classList.remove('nav-type-deeper');
	document.body.classList.remove('nav-type-shallower');
	document.body.classList.add('nav-type-' + navType.toLowerCase());
	return true;
});

useEffect(() => {
	if (blocker.state == 'blocked') {
		const vt = document.startViewTransition();
		vt.ready.then(() => blocker.proceed());
	}
}, [ blocker ]);

This ensures that any time you go deeper in the URL-tree, it runs the 'deeper' animation, and each time you move up it runs the 'shallower' animation, no matter how that navigation action was triggered. On my personal wish list is adding some system to override this on a per <Link> basis though.

Hopefully react-router gets some better support for declaratively specifying view transitions, because the reason for transition (usually) doesn't matter nearly as much, so specifying on a <Link> which is only one of the specific ways to transition feels out-of-place. The above definitely is only a dirty hack to try and get around that limitation.

Hope somebody finds this approach useful :)

goldenice avatar Feb 14 '25 05:02 goldenice