preactjs / preact-router

:earth_americas: URL router for Preact.
http://npm.im/preact-router
MIT License
1.01k stars 155 forks source link

preact-router and preact-transition-group #187

Open mattdesl opened 7 years ago

mattdesl commented 7 years ago

Hi, first of all thanks for Preact and its tooling, it's really great. 👍

I'm looking to add preact-transition-group to my routing so that each component can handle componentWillEnter, componentWillAppear and componentWillLeave.

So far I haven't found a clean way of integrating these two together without patching this module.

https://github.com/Jam3/preact-router/commit/966d4f227c5ae3ab23bf311f2dab86f0dd9761fb#diff-1fdf421c05c1140f6d71444ea2b27638L248

Is there another way this might be possible?

developit commented 7 years ago

Maybe something like this would work, as a wrapper around router?

import Router from 'preact-router';
import TransitionGroup from 'preact-transition-group';

const TransitionRouter = ({ children, ...props }) => {
  <Router {...props}>
    { children.map( child =>
      <TransitionGroup {...child.attributes}>
        {child}
      </TransitionGroup>
    ) }
  </Router>
}

If not, I wondered about just doing it inline:

<Router>
  <TransitionGroup path="/" default>
    <SomeRoute a="b" />
  </TransitionGroup>
  <TransitionGroup path="/one">
    <OtherRoute />
  </TransitionGroup>
  <TransitionGroup path="/two">
    <AnotherRoute />
  </TransitionGroup>
</Router>
mattdesl commented 7 years ago

Thanks for the reply! I tried both but unfortunately neither give me acceptable results.

In your examples you have one transition group per component, but really we should just have one TransitionGroup for all routes/components — that way it can keep track of which components are appearing/leaving in that group.

Also, it seems like the Router only looks at top-level children, so first example is trying to match against TransitionGroups instead of the routes.

So it should ultimately look like this, somehow:

<Router><TransitionGroup>{childrenToMatch}</TransitionGroup></Router>
developit commented 7 years ago

Correct, it only supports one level of children. I think the only way to make this work would be to use a function-as-children approach, which is not currently supported in preact-router, but seems like it could be. Unfortunately the nesting approach you suggested isn't possible because it assumes the component tree to be pre-resolved, whereas children may be derived from some inner render.

<Router>
  { ({ match }) =>
    <TransitionGroup>
      { match([
        <SomeRoute path="/" default a="b" />,
        <OtherRoute path="/one" />,
        <AnotherRoute path="/two" />
      ]) }
    </TransitionGroup>
  }
</Router>

Not sure on the API though. This is a tricky one. Really it'd be nice if Router could pierce the hierarchy, but that'd be quite a bit more complexity. It's something I'd love to do, but it would be a rewrite.

mattdesl commented 7 years ago

Thanks, for now I am happy to use my fork. We eventually might clean it up a bit and re-release it with more documentation on transitions, as an alternative to preact-router for more specialized animated apps.

Cheers. :smile:

developit commented 7 years ago

sounds awesome, looking forward to it!

t47io commented 7 years ago

Also looking for a solution for CSS transitioning between <Route/>s. Tried this https://github.com/developit/preact-router/issues/187#issuecomment-299662622, but does not work as expected.

developit commented 7 years ago

One option here might be to allow passing a "wrap" function to the router. The router would pass the active child through that function, so you could consistently wrap each route in a TransitionGroup:

<Router wrap={ c => <CSSTransitionGroup>{c}</CSSTransitionGroup> }>
  <A path="/" />
  <B path="/b" />
  <C path="/c" />
</Router>

I'm curious why the direct wrapping doesn't work though. Maybe it's because CSSTransitionGroup doesn't passthrough props to its child? Could fix that like so:

import { h, cloneElement } from 'preact';
const TransitionGroup = ({ children, transitionEnter, transitionLeave, component, ...props }) => (
  <CSSTransitionGroup {...{ transitionEnter, transitionLeave, component }}>
    {cloneElement(children[0], props)}
  </CSSTransitionGroup>
);

<Router>
  <TransitionGroup path="/" transitionEnter="enter" transitionLeave="leave">
    <A />
  </TransitionGroup>
  <TransitionGroup path="/b" transitionEnter="enter" transitionLeave="leave">
    <B />
  </TransitionGroup>
</Router>
danalloway commented 7 years ago

@mattdesl would extending the router work for you?

import { h } from 'preact';
import Router from 'preact-router';
import TransitionGroup from 'preact-transition-group';

export default class TransitionRouter extends Router {
    render(props, state) {
        return (
            <TransitionGroup component="div">
                {super.render(props, state)}
            </TransitionGroup>
        );
    }
}
danalloway commented 7 years ago

also, there is this -> https://github.com/prateekbh/liquid-route

fantasia949 commented 7 years ago

@danalloway 's tip works for me, This fiddle shows how to make it work. However, the transition will be broken if I use prateekbh/preact-async-route to wrap route components (both sync and async use).

glegenty commented 4 years ago

Hi ! The tips from @danalloway is not working for me, is this way of using preact router and preact transition group is still relevant ? @fantasia949 i also tried the code from your fiddle, the components seems to not mount with the last version of preact. i'm using :

"preact": "^10.3.2", "preact-router": "^3.2.1", "preact-transition-group": "^1.1.1",

glegenty commented 4 years ago

Finally i managed to make it work with the last version of react-transition-group. I had to switch to react-snap for prerendering, the prerender from preact-cli seems to not work with react-transition-group.