React-router: Discussion: The Switch component is terrible.(?)

Created on 30 Mar 2017  路  11Comments  路  Source: ReactTraining/react-router

I just tried porting my app to RR4 from RR3. Here is my experience.

The <Switch /> component has SERIOUS limitations.

First problem I ran into:
I wanted to guarantee, that only a single route always renders, from 3 different layouts. However there are some "layout components" around each of these "route component render locations". So I can't use Switch for this as it requires Route to be a Direct child of it, to work properly.
I managed to get around this problem, but it still sucks.
Bad proposition: It would be nice if it was possible to defined Switch groups identified e.g. by "id", and the Switch component would guarantee that only a single route in that group is rendered, e.g. by using context.

Second problem I ran into:
I tried to write an HoC for the <Route /> component, which would do a simple thing. Prepend some "standard route prefix" to that route. This is useful now that "nested routes" (as in, incremental path resolution by Route nesting) is no longer possible with RR4.
Because of the <Switch /> component, however, I cannot do this, because it reads the path directly from the children it's passed, it only sees the "path without prefix" of my HoC, not the full path that gets passed to the <Route /> component. So the route doesn't match, and I can't override the path prop on the HoC, as props are "read-only".

Third problem I ran into (not 100% sure):
Is that mount/unmount animation of Routes is impossible with the <Switch /> component, because it gets picked up as a direct child of ReactCSSTransitionGroup and as it is never unmounted, it never triggers a "mount / unmount" animation.
screen shot 2017-03-30 at 5 20 25 pm
Now, I might be misunderstanding something here, because in the docs you specifically say, that Switch is useful for animation. Yet as far as I can tell, it kills the standard use case of ReactCSSTransitionGroup.

I'm very interested in your opinion about these issues, because they seem VERY limiting to me. So much so, that I'm going to probably hold off on migrating to RR4, because I literally can't refactor the app from RR3 and maintain the same set of capabilities that RR3 provides for me.

Last note:
I also can't do without the <Switch /> component, because before in RR3 I could do the following. Specify multiple components for a Route (under some keys), then in my render code, I could do:

if (universalComponent) {
  // render universalComponent
} else {
  // render desktopComponent or mobileComponent
}

I can't do this in RR4, without using the <Switch /> component and "modeling" the else branch as a "match-all" last Route in the <Switch />.

Thank you for your consideration.

Most helpful comment

@vjancik

I too am in the progress of migrating a large app from RR3 to RR4 at the moment and can echo some of these frustrations. Although the they are just components API is a huge improvement in terms of clarity and ease of use, certain things have actually become much harder to do.

Animations in particular are a massive pain right now due to the direct child requirement of Switch and the fact that neither Switch nor Route pass any of their props down to their children. For example, it would be great if we could do:

<Switch>
    <SomeAnimationWrapperComponent>
        <Route />
    </SomeAnimationWrapperComponent>
</Switch>

But because Switch requires direct children to be Route components you have to go through a ton of currying gymnastics to get it to work. And since most animation libraries only animate direct children you can't really do this either:

<SomeAnimationWrapperComponent>
    <Switch>
        <Route />
    </Switch>
</SomeAnimationWrapperComponent>

I wonder if the answer is simply to get a bit more documentation on how to use RR4 with common animation libraries. There is an example right now for how to use it with ReactCSSTransitionGroup and direct Route components, but I found that this example doesn't really carry through once you get into more complex routing needs. It would be great to get some examples of:

  1. ReactCSSTransitionGroup with Switch children (rather than Route children)
  2. react-motion with Route children
  3. react-motion with Switch children

Hope this doesn't come across as "pitchforky". I love the RR4 API so far, and I'd be happy to help with the docs if someone could get me a working example of how to tackle number 1 on that list. I've been at it for several days and still can't find a solution that works well.

All 11 comments

Uh, OK, a lot of complaints here and not a lot of problem solving. oldmanwaggingfingeratyoungwhippersnappers.gif

Look, I get that the new API is different from 2/3, but just issuing a laundry list of complaints doesn't really do anything. It sucks for your specific use case; I really do get that. But not from the perspective of us as library developers. A lot of what 4.0 is about is providing the component "atoms" that you can compose into component "molecules" that get used throughout your entire app.

The first problem is solvable by a very generic component that renders just the first children element it gets (call it <Highlander> for bonus points). For the second problem is just writing a HoC that will manipulate its childrens' props before passing to a <Switch>. And the 3rd requires just looking at the animation docs. You've just gotta do some brain re-wiring for 4.0, but the good news is that rewiring is to think more idiomatically about React itself. It's a win-win for everyone.

So, set the pitchfork down on the ground, munch on a chill pill, and breath deeply. You'll get through this 馃槃

@vjancik

I too am in the progress of migrating a large app from RR3 to RR4 at the moment and can echo some of these frustrations. Although the they are just components API is a huge improvement in terms of clarity and ease of use, certain things have actually become much harder to do.

Animations in particular are a massive pain right now due to the direct child requirement of Switch and the fact that neither Switch nor Route pass any of their props down to their children. For example, it would be great if we could do:

<Switch>
    <SomeAnimationWrapperComponent>
        <Route />
    </SomeAnimationWrapperComponent>
</Switch>

But because Switch requires direct children to be Route components you have to go through a ton of currying gymnastics to get it to work. And since most animation libraries only animate direct children you can't really do this either:

<SomeAnimationWrapperComponent>
    <Switch>
        <Route />
    </Switch>
</SomeAnimationWrapperComponent>

I wonder if the answer is simply to get a bit more documentation on how to use RR4 with common animation libraries. There is an example right now for how to use it with ReactCSSTransitionGroup and direct Route components, but I found that this example doesn't really carry through once you get into more complex routing needs. It would be great to get some examples of:

  1. ReactCSSTransitionGroup with Switch children (rather than Route children)
  2. react-motion with Route children
  3. react-motion with Switch children

Hope this doesn't come across as "pitchforky". I love the RR4 API so far, and I'd be happy to help with the docs if someone could get me a working example of how to tackle number 1 on that list. I've been at it for several days and still can't find a solution that works well.

@EvNaverniouk

Hope this doesn't come across as "pitchforky".

No worries. I hope my response wasn't "dismissive asshat-y"! I'm not trying to sweep this stuff under the rug. More docs are great for everyone (you as a developer and me as a maintainer getting a lot of the same issues).

@pshrmn put together a really useful HoC for your exact use case: https://github.com/pshrmn/rrc/blob/master/docs/wrapSwitch.md

Check out the Why? section at the bottom and this demo: http://codepen.io/pshrmn/pen/aJGZLq

mount/unmount animation of Routes is impossible with the component

FWIW @vjancik you can write your own switch statement in a couple of lines to get animation mounting/unmounting working.

See here for an AnimatedSwitch
See here for example of a site running the AnimatedSwitch code.

@EvNaverniouk

Complaining was not my goal. I didn't provide potential solutions because I was first trying to establish whether I'm doing something wrong, or whether these are real common problems with the V4. It would be quite embarrassing to write out elaborate solutions only to find out I did something wrong in the first place.

The title was admittedly clickbait 馃槃 , apologies.

Okay @timdorr. I can make custom solutions to the problems I encountered, however I wasn't sure this was the "right way" to solve this, as the problems I encountered seemed pretty "standard"? And I felt like the library should handle them without having to rely on overrides and HoCs.

But I understand your intention, please understand mine and once again thank you for your consideration.

The title was admittedly clickbait 馃槃 , apologies.

I Switched to React Router 4 and You Won't Believe What Happened Next!

I can make custom solutions to the problems I encountered, however I wasn't sure this was the "right way" to solve this, as the problems I encountered seemed pretty "standard"? And I felt like the library should handle them without having to rely on overrides and HoCs.

We're decidedly taking a batteries not included approach with 4.0. We had too many opinions in 3.0 and 2.0, and the batteries we were including were weird and not scalable. We took that approach as far as it could go, but it was constantly an issue and missing some edge case. Plus, it forced you to treat the router as this special thing outside of React, so you couldn't apply many idioms and best practices to it, making your job harder and more complex (e.g., render middleware).

4.0 puts more onus on you to solve your own problems. But many of those problems are pretty common and things like rrc are going to start popping up to solve them. (Whomever gets to SSR data prefetching first is going to have a really popular library on their hands!) Right now we're still early on so there isn't an ecosystem yet, but it'll happen over the course of this year.

@timdorr
Regarding a solution you proposed to me:

For the second problem is just writing a HoC that will manipulate its childrens' props before passing to a <Switch>

That HoC would have to be an HoC on the Switch, not the Route, which seems quite hacky, as the routes Can be prefixes while Not having to be under a Switch.

I can't do something like this:

<Switch>
  <RoutePrefixer>
      {/* ... routes */}
  </RoutePrefixer>
</Switch>

because components can't return arrays, so I can't make the RoutePrefixer render the children as an array directly (until React 15).

Which leaves me with only an option of this:

<PrefixedSwitch>
  {/* ... routes */}
</PrefixedSwitch>

which makes the Routes prefix dependent on the Switch, and also doesn't allow me to mix together prefixed and unprefixed routes, under the same switch.

One more problem 馃槄 ...

With the switch component, you can't even make a function which returns routes and renders them inside the switch. Which could be quite problematic for code-splitting, route lazy loading.
My particular case:
screen shot 2017-03-31 at 1 34 24 pm
And the problem visualized in Chrome Dev Tools:
screen shot 2017-03-31 at 1 34 32 pm

It's not obvious from my particular case, but it could also be due to large amount of routes (so you split it into different functions.

<Switch>
  {sectionOneRoutes()}
  {sectionTwoRoutes()}
</Switch>

Or even some "lazy-loading" container.

Now, this could be taken as more complaining, as there are workarounds for this as well.

What I'm trying to say though is, how many problems "with workarounds" do we need to run into before we accept that the Switch element has bad usability?

The bottom line to this is, HoCs and React.Children are things you didn't need to know how to use, to build a React app with routing, before RR4, and that's a major thing to lose, with this release (looking out for the regular Joe).

@vjancik

Which could be quite problematic for code-splitting, route lazy loading.

I haven't had any trouble with code splitting or route lazy loading. Can be done with react-loadable.

<Switch>
  <Route
    path='/'
    exact
    component={asyncComponent(() =>
      System.import('pages/Home').then(module => module.default))}
  />
</Switch>

With the switch component, you can't even make a function which returns routes and renders them inside the switch. Which could be quite problematic for code-splitting, route lazy loading.

You can nest Switch components.

<Switch>
  {isDesktop && <Switch>
    <Route
      path='/'
      exact
      component={asyncComponent(() =>
        System.import('pages/Home').then(module => module.default))}
    />
  </Switch>}
</Switch>

The bottom line to this is, HoCs and React.Children are things you didn't need to know how to use, to build a React app with routing, before RR4, and that's a major thing to lose, with this release (looking out for the regular Joe).

I would argue that those are 2 very important things to learn if you want to be a React developer in the current "meta" of development. The devs are pushing these powerful tools and methods out to be more "mainstream" and common practice which is a great thing IMO.

I'm not trying to call you out or anything, but just take a minute to read the code in the repo. It is not much code and you will learn a ton.

Also this works just fine for me. I don't know why you were having issues.
image

Hmm. I am surprised that renderHome function works. As it creates an array direct child of Switch, and looking through the Switch code, it shouldn't be able to handle that.
Maybe I was wrong on that one.

As for lazy loading. I wanted to lazy load a whole set of routes not a single component in a Route. And yes, I know I can nest Routes, so I can just wrap the whole thing in a Route and lazy load that as a Route component.

But that's a much less cleaner way than introducing some lazy loading HoC / container.

I am talking about flexibility here. I know there are workarounds. But I feel like we are eliminating non-canonical React "hack-arounds" with canonical React "hack-arounds". What I'm wondering is, whether we can do better?

It works because they are using React.Children.forEach which traverses nested, well, anything iterable.

I am talking about flexibility here. I know there are workarounds. But I feel like we are eliminating non-canonical React "hack-arounds" with canonical React "hack-arounds". What I'm wondering is, whether we can do better?

What these guys have done have created a fantastic set of tools for you to create your own solutions for these problems. I think I've shown just how flexible these tools can be. What you want is an abstraction of a common pattern. I may be wrong but it seems like they aren't providing these abstractions anymore. You should create what you are wanting and put it up on npm so others that want the same abstraction can use it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Waquo picture Waquo  路  3Comments

davetgreen picture davetgreen  路  3Comments

maier-stefan picture maier-stefan  路  3Comments

alexyaseen picture alexyaseen  路  3Comments

yormi picture yormi  路  3Comments