Migrating from react-native-router-flux to react-router for web compatibility
Tags: uncategorized, reactpwa, reactnative, reactnativerouter
Published: 2019-11-24 15:02:56

---
title: Migrating from react-native-router-flux to react-router for web compatibility
published: true
date: 2019-08-25 18:10:44 UTC
tags: Uncategorized,react PWA,React-native,react-native-router-
canonical_url: https://ziatechblog.wordpress.com/2019/08/25/migrating-from-react-native-router-flux-to-react-router-for-web-compatibility/
---

Part of our effort to [compile our react-native app for web, so we can have kind of a PWA built in react-native which runs on iOS, android and app, all 3 with same codebase.](https://dev.to/ziaulrehman40/part-1-converting-react-native-app-to-react-native-web-react-pwa-in-monorepo-architecture-4nhi-temp-slug-9666105)  
**We did this migration keeping the interface as close to `react-native-router-flux` as we could so there are minimal required changes in the app** , and in some cases we were able to just change the import and that was it.

## Trying to avoid replacement

**We were using`react-native-router-flux` v4.0 in our RN(0.59.9)** app, this version is based on `react-navigation` v2. When compiling for web, we obviously need compatible libraries. Although `react-navigation` didn’t support web officially in v2, we gave it a try, many things were working, we could navigate just fine. **but issue was, there was no url paths being changed while navigating(among some other issues)**.   
We need to show urls to users so users can bookmark, share or do whatever they want with those urls.

#### Let’s try an upgrade?

[`react-navigation` v3 is officially supporting web now](https://reactnavigation.org/docs/en/web-support.html), although in early stages, there have been a lot of work for web support and efforts are still underway, [but progress on the web part seems a bit slow](https://github.com/react-navigation/web)(at the time of writing) with lot of important use-ability related issues open with little to no efforts to resolve those issues. But as it was most easy to just try upgrading our existing library’s version, we gave it a try.

`react-native-router-flux` v4.1(which is in beta, at the time of writing) uses v3 of `react-navigation`, so we upgraded to that and issues were still there. Issues like:

- No URL paths, only domain name kept showing on all pages.
- Pages were not scrollable on web when bottom navigation was in use.

So as odds were _heavily_ against this setup to work, we decided to just change the library and choose an alternative.

## Why `react-router`?

**`react-router` is the most mature library which supports both native and web** , although packages for web and native are different but API is almost identical, which is a huge benefit, as it allows us to conditionally import(based on OS) and forget about the rest, as API is same(you will see this implemented in a minute).

It is also a good choice for cross platform compatibility because unlike `react-navigation` it only concerns itself with navigation, building UI for bottom or top or whatever navigations is not its job, that reduces parts which needs to be fixed for web and makes compatibility a lot easier.

## Gotchas

Before we jump into conversion, lets discuss some gotchas:

1. Depending upon size of your application, **this can be a lot of rework,** we did worked almost 2 weeks to set our app fully working with this new library.
2. You **can’t pass functions(callback etc) or any other serializable object in state for next route**.
3. You will have to redo any non-routing features you are using from `react-navigation`  
(i am referring to `react-navigation` as `react-native-router-flux` is based on that and all features it gives us are based on those in `react-navigation`).  
What I mean by non-routing features are features like building UI for bottom navigation and anything like that.
4. **To keep the API same, we will use render props pattern** to spread initial props/state and even url params to the component being rendered on any specific route.

## Actual Conversion

First, add these packages: `react-router-dom` and `react-router-native`, make sure their versions match.

Now, we will need to write a couple of special files, which will contain platform specific conditional imports etc. Then, we will use these files to import related components instead of importing from actual libraries. You can consider it kind of wrappers.  
We will be taking an incremental approach, so we will not remove the router-flux in start, rather we will keep it until we are finished with our whole process.

Let’s start with magic files(you can copy these as is).

### Magic Files

#### 1. `Router.js` file:

```
import { Platform } from 'react-native'; 

const isWeb = Platform.OS === 'web'; 

const RouterPackage = isWeb ? require('react-router-dom') : require('react-router-native'); 

export const Router = RouterPackage.Router; 
export default RouterPackage;
```

_ **Disclaimer:** This(and some others) code is taken from somewhere on the web(maybe it was a github issue or something)_.  
Place this file at the location of your preference(we kept it at root, for now). This file is doing nothing fancy, it is just conditionally importing correct library, when its web, we import from `react-router-dom` otherwise we import from `react-router-native`.

#### 2. `RouteHistory` files:

For this, we needed to create two files, first `RouteHistory.js`:

```
import { createMemoryHistory } from "history"; 
export default createMemoryHistory();
```

And for web another file, `RouteHistory.web.js`:

```
import { createBrowserHistory } from "history"; 
export default createBrowserHistory();
```

_If you don’t know what this `.web.js` extension is, and you are not working on a project which will compile to web, you can skip the web.js file. As, keeping it short, `.web.js` file is used instead of `.js` file on the web, when available. Moving on._

**What these files are doing is just creating a history object** , which we will pass to `react-router` so a proper history strategy is used as per platform, `memoryHistory` on mobile and `browserHistory` on web.

### Routes Mapping

**Now we have our magic files in place, now we will start our actual conversion process.**

For routes definition, i made 3 files:

#### 1. `urls.js` file:

This file is simplest, it just contain url definitions:

```
export default {
    applyJob: '/job',
    browse:   '/job/:job_id'
};
```

#### 2. `RouteActions.js` file:

This file will be used to navigate to different routes, it will replace `import { Actions } from 'react-native-router-flux'` with something like: `import Actions from '../RouteActions'`. And hence, will be on forefront for keeping the API same as router-flux’s Actions.

```
import { Platform } from 'react-native';
import history      from './RouteHistory';
import urls         from './urls.js';
import { generatePath } from "react-router";
 
export default class RouteActions {
  static pop() {
    setTimeout(() => {
      history.goBack();
    }, 1);
    return null;
  }
 
  static base(componentProps = {}) {
    history.push('', componentProps);
  }
 
  static post(componentProps) {
    const url = generatePath(urls.post, {postId: componentProps.postId});
    history.push(url, componentProps);
  }
 
  static posts(componentProps = {}) {
    history.push(urls.posts, componentProps);
  }
 
  static refresh(componentProps = {}) {
    history.replace(history.location.pathname, componentProps);
  }
 
  static replace(route, componentProps = {}) {
    history.replace(route, componentProps);
  }
 
  static jump(route, componentProps) {
    history.push(route, componentProps);
  }
}
```

Note that there are quiet a few **extra routes** , like `pop`, `refresh`, `replace`, `jump` and `base`, these routes are implemented as our app was using these routes a lot from `react-native-router-flux`, we tried to maintain as much interface as we could to match that of `Actions` from `react-native-router-flux`. And hence, we had to implement these functionalities.

Secondly, we are receiving `componentProps` and passing them in [`push` and `replace` functions](https://github.com/ReactTraining/history#navigation). This is also for the effort to match `Actions` behavior in `react-native-router-flux`, you will more details about this in `Routes.js` file description.

You might also have noticed that `componentProps` is optional in all other routes, but not in `post` function/route. Why?  
This is because we are expecting an object **will** be passed, which **will** contain at-least `postId`, and can contain any other props it want to pass to the component being rendered o the route.

Take your time to try to make sense of this file. Then continue.

#### 3. `Routes.js` file:

**This file will be a replacement of old `react-native-router-flux` routes definitions** , whether that was in a separate file or mixed up in `App.js`, this new file will replace those routes definitions and will actually be imported in `App.js` to render routes replacing router-flux’s import.

For the sake of simplicity, let’s assume there are only three routes we have to map:

1. `/posts` (is home page after signin)
2. `/post/:post_id`
3. empty/root route which renders home page

It will look something like:

```
import React, {Component} from 'react';
import Routing, { Router } from './wrappers/Router/Router';
import history from './RouteHistory';
import urls from './urls.js';

//Screens
import Posts from './screens/Posts';
import Post from './screens/Post';
import Home from './screens/Home';

const { Route, Switch, Redirect } = Routing;

class Routes extends Component {
    render() {
        return (
            <Router history={history}>
                <Switch>
                    <Route exact path='' component={Home}/>
                    <Route exact path={urls.posts}
                        component={(props) => <Posts {...props} {...props.location.state}/>}
                    />
                    <Route exact path={urls.post}
                        component={(props) => <Post {...props} {...props.location.state}/>}
                    />
                    <Redirect to="/" />
                </Switch>
            </Router>
        );
    }
}

export default Routes;
```

We are importing our three screens and rendering them based on the url, lets break this down further:

`Switch` is a component which is used to render something based on some condition, it stops looking for matches whenever it finds first match, [you can read docs for more info on this.](https://reacttraining.com/react-router/web/guides/basic-components)

If we are on root path, without any sub paths, it will render `Home` component, as you can see in the code snippet.

If we are at `/posts` route, it will render `Posts` component, but that is a bit different, it is using [render props pattern](https://reactjs.org/docs/render-props.html) here, let’s discuss why.  
You can notice we are spreading a couple things while rendering component:

```
component={(props) => <Post {...props} {...props.location.state} {...props.match.params}/>}
```

This is to match the `react-native-router-flux`‘s interface for navigating(which is something like  
`Actions.post({postId: 100, someOthrProps: 'value'})`

1. `{…props}` : this is spreading default props from react-router, this includes `history`, `location` and `match`(and maybe more). Please explore [docs](https://reacttraining.com/react-router/web/guides/quick-start) for details.
2. `{…props.location.state}` this will spread any values we are passing when routing with `Actions.post` just like `react-native-router-flux` does, [it calls this PARAMS in its docs](https://github.com/aksonov/react-native-router-flux/blob/master/README2.md#usage). So from our example `Actions.post({postId: 100, someOthrProp: 'value'})`, post component will receive two props `postId` and `someOthrProp`.   
_This is same concept from router-flux, if you are here reading this doc on replacing flux, I assume you have worked on router-flux and will understand what I am talking about here._

#### Lets use this new router

Now we can open our `App.js` file and start using this router, something like this(it is simplified a lot, of-course):

```
import React, {Component} from 'react';
import { View } from 'react-native';
import Routes from './Routes';

export default class App extends Component {
    render() {
        return (
            <View>
                <Routes/>
            </View>
        );
    }
}
```

Not to mention, you need to remove `react-native-router-flux`‘s import and initialization part in here.

#### Finally, replace imports:

At this stage, all your routing will be broken, you will be going to base/home route just fine, and any links or redirections will throw errors, thats because we have removed flux initialization from `App.js` and still trying to route using flux(in other components), lets start fixing our imports from:

```
import { Actions } from 'react-native-router-flux'
```

To:

```
import Actions from '../RouteActions'; //fix path obviously
```

And most routes should work, given that you have mapped all your urls in all 3 files, `urls.js`, `Routes.js` and `RouteActions.js`.  
_(This 3 file structure is obviously optional.)_

Lookout and workout the gotchas and incrementally check and fix all links/redirects mapping routes in appropriate files.

#### Are we finished?

Not if you have `deeplinking` or universal links in your apps, in that case a little extra work might be required. We will not look into that here. you will have to figure that out yourself.

And we are done!

Thanks for bearing with me, do let me know if some parts in this article are hard to follow_(I understand there might be, but for someone who has worked properly with `react-native-router-flux`, this should not be hard to understand, still I am open to suggestions for improvement.)_

Cheers!