Skip to content

Instantly share code, notes, and snippets.

@kennetpostigo
Last active June 2, 2021 17:44
Show Gist options
  • Star 64 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kennetpostigo/7eb7f30162253f995cd4371d85c1e196 to your computer and use it in GitHub Desktop.
Save kennetpostigo/7eb7f30162253f995cd4371d85c1e196 to your computer and use it in GitHub Desktop.
How I migrated from ReactRouter v2 to v4

First couple things I thought about when migrating after reading the docs

So migrating my existing app wasn't as troublesome as I originally thought. First thing I did was take a look at my router and routes and figure try to make a mental model of all the files where I had nested routes in the existing app because those components/containers will contain {this.props.children}. So I need to replace those with the nested <Match /> components.

So just to give an example:

In v2:

<Router history={history}>
  <Route path="/" component={App}>
    <IndexRoute component={LandingContainer}/>
    <Route path="profile/:username" component={UserProfileContainer}/>
    <Route path="reviews/:username" component={ReviewsContainer} />
    <Route path="userhome" component={UserHome}/>
    <Route path="item/:id" component={ItemPageContainer}/>
    <Route path="event/:id" component={EventPageContainer} />
    <Route path="listings(/:searchTerm)" component={ListingsContainer}/>
    <Route path="checkout" component={CheckoutContainer} />
    <Route path="dashboard" component={Dashboard}>
      <IndexRoute component={DashCartContainer}/>
      <Route path="dashtrades" component={DashTradesContainer}/>
      <Route path="dashmanageitems" component={DashManageItemsContainer}/>
      <Route path="dashmanageevents" component={DashManageEventsContainer}/>
      <Route path="dashorderhistory" component={DashOrderHistory}/>
      <Route path="dashevents" component={DashEventsContainer}/>
      <Route path="dashtracking" component={DashTracking}/>
      <Route path="dashshipinfo" component={DashShipInfoContainer}/>
      <Route path="dashbillinfo" component={DashBillInfoContainer}/>
      <Route path="dashcustomerservice" component={DashCustomerServiceContainer}/>
      <Route path="dashaccountsettings" component={DashAccountSettings}/>
    </Route>
  </Route>
  ...
  <Route path="*" status={404} component={NotFound}/>
</Router>

In v4:

<Router>
  <div>
    <Match pattern="/" component={LandingContainer}>
    <Match path="profile/:username" component={UserProfileContainer}/>
    <Match path="reviews/:username" component={ReviewsContainer} />
    <Match path="userhome" component={UserHome}/>
    <Match path="item/:id" component={ItemPageContainer}/>
    <Match path="event/:id" component={EventPageContainer} />
    <Match path="listings(/:searchTerm)" component={ListingsContainer}/>
    <Match path="checkout" component={CheckoutContainer} />
    <Match path="dashboard" component={Dashboard} />
    <Miss component={NotFound}/>
  </div>
</Router>

Couple things change from v2 to v4:

  1. No longer need to add history prop on <Router />

  2. Need to wrap children of <Router /> within a <div /> (mistake that I made initially to not wrap in <div /> Will throw error)

  3. <Route /> -> <Match />

  4. <Match /> takes a pattern prop to match to a url like /RRv4IsGreat

  5. Dedicated component to catch invalid not "registered" urls with <Miss />

  6. I removed all nested routes. This is because since now all of the components that are exported from ReactRouter are really just components you can just put components down a container component as its children and it works like magic!

So instead of having all nested routes inside a "Main" router you can just put them in a container like:

class Dashboard extends React.Component {
  render () {
    return (
      <div>
        <DashMenu pathname={this.props.pathname}/>
        <Match exactly pattern={this.props.pathname} render={() => <DashCartContainer />}/>
        <Match pattern={`${this.props.pathname}/dashtrades`} component={DashTradesContainer}/>
        <Match pattern={`${this.props.pathname}/dashmanageitems`} component={DashManageItemsContainer}/>
        <Match pattern={`${this.props.pathname}/dashmanageevents`} component={DashManageEventsContainer}/>
        <Match pattern={`${this.props.pathname}/dashorderhistory`} component={DashOrderHistory}/>
        <Match pattern={`${this.props.pathname}/dashevents`} component={DashEventsContainer}/>
        <Match pattern={`${this.props.pathname}/dashtracking`} component={DashTracking}/>
        <Match pattern={`${this.props.pathname}/dashshipinfo`} component={DashShipInfoContainer}/>
        <Match pattern={`${this.props.pathname}/dashbillinfo`} component={DashBillInfoContainer}/>
        <Match pattern={`${this.props.pathname}/dashcustomerservice`} component={DashCustomerServiceContainer}/>
        <Match pattern={`${this.props.pathname}/dashaccountsettings`} component={DashAccountSettings}/>
      </div>
    );
  }
}

Couple things you may notice here:

  1. The first Match you see there has a prop called render which you can kind of think of as the <IndexRoute /> and it will render what ever component you pass it when it hits the "index" route of the nested view.

  2. You don't need to wrap the nested views within a <Router />. (Probably since the original router passes down props or context)

  3. I can render other components along side the <Match /> components! Like a menu for my nested view.

  4. I pass the pathname prop down to the menu component, specifically for the <Link />

Found that its super helpful and easy in v4 to get access to the pathname and location because in v2 you had to use withRouter or something like that (not sure on the name because I began using react-router-redux so that I could just pull of props, among other reasons).

My Links didn't really change much, only real changes we're changing the to prop to take the props.pathname instead of the one I used to get from react-router-redux:

<div className={css(styles.DashTabs)}>
  <ul className={css(styles.tabList)}>
    <Link activeClassName={css(styles.activeTab, styles.noLeft)} to={`${props.pathname}`} activeOnlyWhenExact={true} className={css(styles.tab, styles.noLeft)}>Item</Link>
    <Link activeClassName={css(styles.activeTab)} to={`${props.pathname}/dashtrades`} className={css(styles.tab)}>Trades</Link>
    <Link activeClassName={css(styles.activeTab)} to={`${props.pathname}/dashmanageitems`} className={css(styles.tab)}>Manage Item</Link>
    ...
  </ul>
</div>

Aside from that the rest of my app stayed unchanged.

Last Remarks

The change from v2 to v4 honestly felt really really really good. Because it felt like the surface of what react-router should do shrunk dramatically. This for me personally was awesome! Because I for the most part rarely/never used anything but the routing from react-router. Also you can treat them like regular components and throw <Match /> in any component whenever you want instead of having this one giant <Router />. My teammates and I didn't feel it was really that painful.

We will soon remove react-router-redux from our project because one of my teammates we're telling me that we can programmatically route with v4 by hooking into <BrowserRouter /> onPush prop which is awesome 🤘. I think hes writing an HOC to do this but hes actually currently working on it at the moment.

Im no expert of React-Router, I just saw the new API and saw some people really hating on it on twitter and wanted to see how bad it would actually be to actually migrate to it without speculating. Turns out not so bad! The migration took about 15-25 minutes tops. We we're able to keep all our Auth and Search Logic exactly the same! Big thank you to @ryanflorence & @mjackson for v4! Looking forward to opensourcing and contributing to some addons that my teammates and I were working on at Stela today!

@ForkInSpace
Copy link

@fzaninotto I've looked through your guide and fixed my {this.props.children} issue. Thx a bunch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment