Typed react native router

Note, a seasoned UI friend of mine told me this is a terrible way to do things and to instead use hooks instead

I decided to give react native a go the last few days, just to try something different. For what it’s worth I haven’t done any real front-end work in quite a bit. The last stuff I did was angular 2/(8/a million?) and it was internal dev tooling stuff that was kind of hacky.

Seems like the javascript world has come quite a distance since back when typescript was still in beta!

That said, I dove into a sample react native app that I scaffolded out using expo. Then I popped openn IntelliJ and started to play with react-native-paper and react-navigation.

The first thing I wanted to see how to do properly was to do DI, since in my mind without clearly having DI you can’t build large scale applications. There’s a lot of ways to do DI but you need to make sure to not couple your code to its dependencies. Seems like React’s answer to that is props, contexts, and redux. I haven’t looked much at redux yet, but my initial reaction to props and context was lackluster. Props smells like a giant property god object to pass around and that never ends well.

However, typescript does make this simpler and I do kind of like the higher order container stuff.

After making a little hello world app and playing with the react router, I wanted to see if we could type the router. For example, in all the examples I can see you use

 this.props.navigation.navigate("Home")

To navigate around. This screams bad design to me because I can tell already people will start sprinkling in static strings everywhere, running into typos and other hard to track down problems. What I would rather have is

 this.navigation.home()

Which is typed, discoverable, and easy to re-use.

However, it took me quite a bit to figure out how to inject

  1. Capture an instance of props
  2. Wrap the navigation object into an object we can type
  3. Pass that object to a component

Turns out higher order containers were needed and the magic sauce is here. First I can make my routable class that given an instance of the screen router can do the things it wants to do.

type RoutableProps = {
    navigation: NavigationScreenProp<any, any>
}

class Routable {
    private navigation: NavigationScreenProp<any, any>;

    constructor(navigation: NavigationScreenProp<any, any>) {
        this.navigation = navigation;
    }

    home() {
        console.log("home")
        this.navigation.navigate("Home")
    }

    back() {
        console.log("back")
        this.navigation.goBack()
    }

    profile() {
        console.log("profile")
        this.navigation.navigate("Profile")
    }
}

Assuming we have this (somehow) we can use it in our nav bar

interface NavProps {
    router: Routable
}

export class NavBar extends React.Component<NavProps> {
    render() {
        const navigate = this.props.router;

        return (
            <Appbar>
                <Appbar.BackAction onPress={() => navigate.back()}/>
                <Appbar.Action icon="archive" onPress={() => navigate.home()}/>
                <Appbar.Action icon="mail" onPress={() => navigate.profile()}/>
            </Appbar>
        );
    }
}

Now here we can wrap a higher order container to capture the props, inject the setting we want, and return a new decorated component

function withRouter(Component) {
    // inject the navigation route to the props of the component
    return withNavigation(
        class extends React.Component<RoutableProps> {
            render() {
                const nav = this.props.navigation;
                // give the component a router
                return (
                    <Component router={new Routable(nav)}/>
                );
            }

        }
    );
}

export const Nav = withRouter(NavBar);

Now we can safely use our typed navigation component

export default function Home() {
    return (
        <PaperProvider theme={theme}>
            <SafeAreaView style={styles.bottom}>
                <Nav/>
                <Title>Hello</Title>
            </SafeAreaView>
        </PaperProvider>
    );
}

And whamo! Typed navigation.

Full source available at my github https://github.com/devshorts/react-native-samples