react-router v4 with react-native

*This is very much out of date now. React router v4 final was released and there are a lot of changes from below. The documentation is excellent and you should really check it out: https://reacttraining.com/react-router/native/guides/quick-start

Yesterday the team behind react-router caused a bit of a stir by making breaking changes…again. I haven’t really been keeping track, but if my semver math is right I’d say they’ve done this at least 3 times now. I thought it might be fun to join in the drama and complain that my free software isn’t exactly what I want, but unfortunately I really like the new direction. After some minor backlash, Ryan Florence updated the github README with some more background as to why they’ve made the change. I suggest you read it if you haven’t. It does a good job of explaining their reasoning.

At the moment, I’m lucky to be at the beginning of a major product refactoring and I’m really looking forward to using the new router. I have lots of ideas for ways to isolate components/routes/features using the new routing. Part of our refactoring is a move to React Native for our native mobile applications. We’re trying hard to share as much code as possible between all of our front-end implementations(no server just yet, but I’m working on it). As such I’ve wanted to try web style navigation on our mobile app for a while now(I’m not a huge fan of the scene push/pop paradigm), and was curious if I could use the new react router in react-native. Browsing the docs I saw that the MemoryRouter should be capable of handling it, so I figured I’d give it a shot. I couldn’t find any examples, so I tentatively hopped in, expecting to find massive hurdles, but was pleasantly surprised to have to basic concept working in 30 minutes or so.

Setup

To get started I created a new react-native project

react-native init RR4Native

next we’ll be adding in the react-router dependency

npm install --save react-router@next

At this point I got an warning about react-dom missing. As best I can tell this is primarily for BrowserRouter support, so I think we’re ok without it.(Update: confirmed, and this dependency is removed in the next release)

Take a minute to gut the index.ios.js file. I removed all the styles except the container style, as well as deleted everything inside the render method.

Next up we need to import the important parts of react-router. Inside index.ios.js(android steps should be similar if not identical), add the following import near the top of your file:

import { Match, MemoryRouter as Router } from 'react-router';

To setup the Router, we just add a Router component to our render method, like so:

class RR4Native extends Component {
render() {
return (
<Router>
</Router>
)
}
}

I then added a View inside the Router. As an aside, I was only able to get the Router working with a single child view. I need to look into whether that is an intentional choice, or an enhancement waiting to happen.

class RR4Native extends Component {
render() {
return (
<Router>
<View style={styles.container}>
</View>
</Router>
)
}
}

Next up we’re going to create our routes. The react router docs do a good job of explaining this, but you basically just give it a pattern and a component to render when the location matches that pattern. In our case we’re going to make 3 paths, /, /about, and /topics. Since the components themselves don’t really matter for our example I made a quick helper function to create the components.

const componentFactory = (routeName) => () => (
<Text style={styles.route}>{routeName}</Text>
)

Now we can add our routes:

render() {
return (
<Router>
<View style={styles.container}>
<Match exactly pattern="/" component={componentFactory('Home')}/>
<Match pattern="/about" component={componentFactory('About')}/>
<Match pattern="/topics" component={componentFactory('Topics')}/>
</View>
</Router>
)
}

Lastly, we’ll be creating links to our new routes. Unfortunately we can’t use the Link component to help us. The link component currently spits out an anchor tag element, which works well on web, but gives us errors on native as we don’t have anchor tags. To get around this I made a dumb NavLink component which acts just like the Link component on web.

const NavLink = ({to, children}, context) => {
const pressHandler = () => context.router.transitionTo(to);
return (
<TouchableHighlight onPress={pressHandler}>
{children}
</TouchableHighlight>
)
}
NavLink.contextTypes = {router: React.PropTypes.object}

There are probably better ways to do this, but it works well for our test. Don’t forget to import the TouchableHighlight component. Now we just have to create our links with our new NavLink component.

<View style={styles.container}>
<NavLink to="/">
<Text style={styles.routeLink}>
Home
</Text>
</NavLink>
<NavLink to="/about">
<Text style={styles.routeLink}>
About
</Text>
</NavLink>
<NavLink to="/topics">
<Text style={styles.routeLink}>
Topics
</Text>
</NavLink>
</View>

That’s pretty much it. You should now be able to navigate throughout your app just by touching your new NavLink buttons. One of the reasons I’m most excited about this is the ease with which you’ll be able to separate routes. Since they’re all just components now you can just import/export them throughout your app. Maybe I’ll even implement that deep linking I’ve been wanting to try out. Let me know what you think. Also, if you’d rather just cut and paste the code, including a bonus broken route handler, you can go here: