molefrog / wouter

🥢 A minimalist-friendly ~2.1KB routing for React and Preact
https://npm.im/wouter
The Unlicense
6.41k stars 146 forks source link

Exit animation with framer-motion #414

Closed mohas closed 4 months ago

mohas commented 5 months ago

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

molefrog commented 5 months ago

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

mohas commented 5 months ago

sorry for the confusion it can be viewed now

molefrog commented 5 months ago

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>
  );
}
mohas commented 5 months ago

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

molefrog commented 5 months ago

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 commented 4 months ago

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

eriksachse commented 3 months ago

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.