Are there any plans on how somebody could realise animated transitions between routes? In v5 we could use the location prop of the <Switch /> component, but in the new <Routes /> component there isn't any replacement for this. Or is there an other way to do it in v6?
I think two things need to happen:
useRoutes needs to accept a provided location. options object.The latter is optional, but more scalable as an API.
Then adding a location prop to <Routes> is easy.
This sounds good to me. For me it would be fine to only add it to the useRoutes hook. <Routes /> is just a wrapper around it and can be implemented on my own.
Related pull request: #7298
Can someone explain the advantages of adding location in Routes with a usage example?
I personally don't use 3rd-part transition/animation library, and can do animated page transition using useNavigate, useEffects and css.
In the example below, I set a timeout of 500ms in an effect, and a 500ms opacity transition when clicking on a button.
export default function Welcome() {
const navigate = useNavigate();
const [path, setPath] = useState<string | null>(null);
function handleClick(path: string) {
setPath(path);
}
useEffect(() => {
if (path) setTimeout(() => navigate(path), 500);
});
return (
<div
className={path ? cn(container, "transition-opacity", "duration-500", "ease-linear", "opacity-0") : container}
>
<Button
onClick={() => handleClick("/discover/")}
className={pinkButton}
disabled={path !== null}
>
<span className="m-auto">Discover</span>
</Button>
I'd like to find a similar approach that would be declarative, though.
@AdrienLemaire Well, within a <Routes /> there can be only one page active at a time. When you want a transition between two pages you want two pages to be active at a time. That means that you need two locations to be used at a time. <Routes /> (like <Switch /> in RRv5) can only handle one location at a time. Therefore you need a way to tell one <Routes /> to use the current location and the other one to use the upcoming location. And here <Routes location /> comes in handy.
@MeiKatz thanks for the clarification, makes sense in order to show parts of both pages at the same time during the animation.
Another thought: Wouldn't the useDeferredValue hook from upcoming concurrent mode also help with that, providing an alternative to the Routes location solution? I have not yet managed to get it working in my case with the experimental branches.
@AdrienLemaire The useDeferredValue hook might help here, but it doesn't challenge the use case of <Routes location />. Therefore the both features might go well together.
Edit: I thought a little bit further: I don't think that useDeferredValue will add any value to this. A AnimatedRoutes component might look like this:
function AnimatedRoutes({
children,
classNames,
timeout,
...rest
}) {
const location = useLocation();
return (
<TransitionGroup {...rest}>
<CSSTransition
key={location.key}
timeout={timeout}
classNames={classNames}
>
<Routes location={location}>
{children}
</Routes>
</CSSTransition>
</TransitionGroup>
);
}
You see? No need for useDeferredValue.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
You can add the fresh label to prevent me from taking any action.
This issue is still fresh even so there is a pending pull request.
Possible alternative approach with current release without location on Routes, depending how complex your use case...
I have always done these routes transitions using a <TransitionGroup> wrapped around the <Routes> (well, <Switch>) and to pass the location so we can have 2 states of the
Note: When using React Transition Group with React Router, make sure to avoid using the Switch component because it only executes the first matching Route. This would make the exit transition impossible to achieve because the exiting route will no longer match the current URL and the children function won't execute.
In their example for v5, they just map over the routes (no parent Switch) and use the function child variant of Route to decide if the child route component is rendered or not based on the match. See below:
<Router>
...
{routes.map(({ path, Component }) => (
<Route key={path} exact path={path}>
{({ match }) => (
<CSSTransition
in={match != null}
timeout={300}
classNames="page"
unmountOnExit
>
<div className="page">
<Component />
</div>
</CSSTransition>
)}
</Route>
))}
...
</Router>
From what I can tell, v6's <Route>s dont support this functional children variant. They do however have hooks we can use to do the same. So I did the following:
<Routes /> (possibly now going to break other code since it doesnt create a new context, anyone care to comment?). <Route /> component that looks something like this:const MyRoute = ({ path, element }) => {
const resolvedPath = useResolvedPath(path);
const match = useMatch(resolvedPath.pathname);
return (
<CSSTransition
in={match != null}
timeout={300}
classNames="page"
unmountOnExit
>
<div>
{element}
</div>
</CSSTransition>
);
}
<BrowserRouter history={history}>
...
{routes.map(({ path, element }) => (
<MyRoute key={path} path={path} element={element} />
))}
...
</BrowserRouter>
The reason this works is because all <MyRoutes /> get rendered, and the decision to render the element is deferred to the <Transition>'s in={match != null} prop. One nice property of this approach is being able to keep a Route rendered but hidden by changing the properties on the Transition to unmountOnExit={false} mountOnEnter={true}.
I have a feeling that <Routes /> does more than this simple case, so removing it may break nested routes. Has anyone else got any other feelings on this approach?
Right now, I'm using this in one of my nested route outlets for a specialised case. Seems to be doing what I expect.
@woollsta You're right, but the approach I realised in my referenced PR makes it a lot easier. Currently the problem is, that @mjackson and @ryanflorence are busy doing other stuff and therefore RRv6 makes no progress at all. Hopefully this will change in the near future. Would be a pity if not.
This is likely a stupid question, but is there a way to reference the patched branch in my project so I can try it out prior to it's eventual merge? I gave it a go, but I think bc of the way the monorep is set up I'm not able to reference the packages inside. E.g. tried yarn add https://github.com/smvv/react-router.git#add-location-prop to no avail.
You can clone my repository and run the install script..
in my project, I didn't use Switch either to achieve animation. Instead, I found that at least in v5, Route exposes a computedMatch props, which enables me to use matchPath to get the matched route and its matched params (something like <Route computedMatch={matched}><matchedRoute.component/></Route>.
I also use react-transition-group, which remembers the props of the last (the going to disappear) route rendering. So that it does not give the incorrect matched params while the animation happens.
I haven't tried with v6 yet. But hope that there will be another workaround if the computedMatch props is removed.
Most helpful comment
This issue is still fresh even so there is a pending pull request.