wouter icon indicating copy to clipboard operation
wouter copied to clipboard

Exit animation with framer-motion

Open mohas opened this issue 2 years ago • 5 comments

Hi, I've setup a simple demo to demonstrate that exit animation using framer-motion package does not work, does anyone have any idea how to make it to work?

https://codesandbox.io/p/sandbox/elastic-violet-9t9r4h

here is documentation on AnimatePresense component: https://www.framer.com/motion/animate-presence/

any ideas or contributions are welcome thank you

mohas avatar Feb 12 '24 19:02 mohas

Is the link correct? I got 404 when I try to open the demo

molefrog avatar Feb 12 '24 22:02 molefrog

sorry for the confusion it can be viewed now

mohas avatar Feb 13 '24 09:02 mohas

Per framer-motion docs:

AnimatePresence works by detecting when direct children are removed from the React tree.

The issue is that Route is the direct child here, not the route's content. You might need to perform matching manually to conditionally render the child of AnimatePresence like so:

export default function RouteAnimated() {
  const [match] = useRoute("/")

  return (
    <div className="App">
      <AnimatePresence mode="wait">
        {match &&
          <motion.div
            initial="in"
            animate="stay"
            exit="out"
            variants={animateVariants}
            transition={{ duration: 1 }}
          >
            Page 1
            <br />
            <Link to="/2">To 2</Link>
          </motion.div>
          }
      </AnimatePresence>
    </div>
  );
}

molefrog avatar Feb 14 '24 14:02 molefrog

if I do it like this would I be losing anything that wouter offers, what will be the pros and cons?

mohas avatar Feb 16 '24 04:02 mohas

if I do it like this would I be losing anything that wouter offers, what will be the pros and cons?

I know but there is currently no way to achieve that using Route. Even if you look at the React Router example from framer-motion docs, you will see that they use useRoutes instead to manually get the element needed for rendering.

We don't ship useRoutes yet, but I spent some time implementing it using current API, and it worked!

const useRoutes = (routes) => {
  // save the length of the `routes` array that we receive on the first render
  const [routesLen] = useState(() => routes.length);

  // because we call `useRoute` inside a loop the number of routes can't be changed!
  // otherwise, it breaks the rule of hooks and will cause React to break
  if (routesLen !== routes.length) {
    throw new Error(
      "The length of `routes` array provided to `useRoutes` must be constant!"
    );
  }

  const matches = routes.map((def) => {
    return useRoute(def.path);
  });

  for (let [index, match] of matches.entries()) {
    const [isMatch, params] = match;

    if (isMatch) {
      return cloneElement(routes[index].element, { params });
    }
  }

  return null;
};

I think I can update the README and add this to the FAQ. I would also consider adding useRoutes, though I am not sure yet if this should be part of the core.

molefrog avatar Feb 20 '24 13:02 molefrog

I have added an FAQ item to the README on using wouter with framer-motion.

molefrog avatar Feb 26 '24 14:02 molefrog

Here is a working CSB: https://codesandbox.io/p/sandbox/romantic-wind-s5ghpj GitHub in case I have to remove it from CSB: https://github.com/eriksachse/romantic-wind-s5ghpj

Edit: Make sure to use {cloneElement(element, { key: location})} instead of {cloneElement(element, { key: location.pathname })} I think something changed somewhere.

eriksachse avatar Mar 27 '24 10:03 eriksachse